无限滚动游戏的复杂性

要点:重复使用您的 DOM 元素,并移除离 视口使用占位符处理延迟的数据。这里有一个 演示以及无限可能的代码 滚动条。

无限滚动条在互联网上随处弹出。Google 音乐的音乐人列表 Facebook 的时间轴和 Twitter 的实时 Feed 都一样您 在您滚动到底部之前,系统会神奇地显示新内容 看起来好像是凭空出现的它可以为用户提供顺畅的体验,并且 了解申诉内容。

然而,无限滚动网页背后的技术挑战比现在更难 看起来当您想要做《The Right ThingTM》时遇到的一系列问题 非常庞大它从简单的开始,比如页脚中的链接变为 用户实际上无法访问,因为内容不断将页脚推开。但是 越来越难。当用户转动自己的设备时,如何处理调整大小事件 手机从纵向显示转为横向,或者如何防止手机磨损 列表太长时令人痛苦的停下来?

正确的做法 TM

我们认为,这足以提供参考实现 展示了如何以可重复使用的方式解决所有这些问题, 保持性能标准

我们将使用 3 种技术来实现我们的目标:DOM 回收、Tombstone 以及滚动锚定

我们的演示案例将是一个类似于 Hangouts 的聊天窗口,在该窗口中,我们可以滚动 通过邮件传递。首先,我们需要一个无限的聊天来源 消息。从技术层面来讲,市面上没有真正的无限滚动游戏 可以注入无限量数据, 滚动浏览者的体验为了简单起见,我们只对 一组聊天消息,并从中挑选 留言、作者和偶尔的图片附件 略微随机添加一些人为延迟的内容 真实网络。

Chat 应用屏幕截图

DOM 回收

DOM 回收是一项未充分利用的技巧,用于保持 DOM 节点数较少。通过 一般的想法是,改用已创建的位于屏幕外的 DOM 元素 也就是创建新消息不可否认,DOM 节点本身的成本很低 它们不是免费的,因为每种方法都会在内存、布局、样式和绘制方面增加额外的开销。 如果低端设备并非完全无法使用,那么运行速度会 网站因为 DOM 过大而无法管理。另请注意,每次重新布局 和重新应用样式 - 当某个类 在节点中添加或移除数据 - DOM 越大,开销就越大。 回收 DOM 节点意味着,我们将保留 DOM 节点总数 节点数量大幅减少,从而加快所有这些进程的运行速度。

第一个障碍是滚动本身。由于只有很小一部分 那么就需要另辟蹊径 使浏览器的滚动条能够正确反映 我们将使用 1x1 像素的标记元素, 强制让包含商品的元素 - 跑道 - 获得期望的 高度。我们会将 T 台中的每个元素分别提升到各自的层次, 确保跑道这层完全空空如也无背景颜色 什么都不用做。如果跑道的图层不为空,就没有资格使用浏览器的 而且我们必须在 高度为几十万像素绝对不可行 。

无论何时滚动,我们都会检查视口是否已经足够接近 跑道尽头。如果确实如此,我们将通过移动哨兵来延长跑道 元素,并将离开视口的项移到 并在其中填充新内容。

<ph type="x-smartling-placeholder"></ph> 时装秀 哨兵 视口

在其他方向滚动时也是如此。不过,我们绝不会 缩小我们实现中的跑道 让滚动条位置保持不变 保持一致。

Tombstone

如前所述,我们会尽力让数据源的行为 真实世界。考虑到网络延迟及其他因素。这意味着,如果 用户会使用翻动滚动的方式滚动浏览上一个元素 我们有数据如果发生这种情况,我们会放置一个墓碑项 - 占位符 - 替换值后, 数据到达。Tombstone 也会被回收,并有一个单独的池 可重复使用的 DOM 元素。我们需要这样才能顺利地从 为填充了内容的项分配 Tombstone,否则, 对用户感到厌烦,甚至可能使他们忘记

此类
坟墓。非常石头。哇

一个有趣的挑战是,真实物品的高度可以大于 Tombstone 项,因为每个项目的文本量不同,或所附加的 图片。为了解决此问题,我们每次都会调整当前的滚动位置 锚定广告 滚动位置传递给元素,而不是像素值。这个概念是 称为滚动锚定

滚动锚定

当将 Tombstone 替换成 以及调整窗口大小时(在设备 会被翻转!)。我们必须确定最顶层的可见元素, 与视口大小相同由于该元素可能只有部分可见,因此我们还将 存储相对于元素顶部(视口起始位置)的偏移量。

滚动锚定示意图。

如果视口大小经过调整且跑道发生了变化,我们就可以恢复 看起来跟用户一模一样的情况大获全胜!除了 窗口意味着每个项的高度都可能改变过,那么我们如何 您知道应该将锚定内容放置在下面多远的位置吗?我们没有!了解 我们必须布局锚定项上方的每个元素,并将所有元素相加, 自己的身高这可能会导致调整大小后出现明显的暂停,我们 也想做这件事相反,我们假定上述每一项都具有相同的尺寸 Tombstone,然后相应调整滚动位置。由于元素 我们可以调整滚动位置,从而有效地 直到实际需要时才会工作

布局

我跳过了一个重要的细节:布局。每次回收 DOM 元素 通常会重新安排整个 T 台的布局,这会使我们的排名远低于 将目标帧速率设为每秒 60 帧为了避免这种情况, 布局,并通过转换使用绝对定位元素。 这样,我们就可以假设 T 台靠后的所有元素仍然 实际上是仅有空白区域时,还是占据了空间由于我们正在 我们可以缓存每个列表项的结束位置, 当用户向后滚动时,立即从缓存中加载正确的元素。

理想情况下,这些项在附加到 DOM 后只会重绘一次 并且不会为 T 台上其他物品的增减而感到震惊。也就是 但仅适用于现代浏览器。

新潮剧情

最近,Chrome 增加了对“CSS 包含”功能的支持。 可让开发者告诉浏览器,元素是 就能轻松完成由于我们要自己进行布局,因此 实施遏制扩散措施每当我们向 T 台增添元素时,我们都知道 其他项无需受到重新布局的影响。因此,每一项都应 获得 contain: layout。我们也不想影响网站的其余部分 所以 T 台本身也应获得此样式指令。

我们还考虑过 IntersectionObservers 作为检测 当用户滚动浏览到足够多时,我们可以开始回收元素并加载新的 数据。不过,IntersectionObserver 的延迟时间较长(就好像 使用 requestIdleCallback),因此我们实际上可能会觉得响应速度变慢 IntersectionObserver 之用。即使是我们当前的实现,使用 scroll 事件会受此问题影响,因为滚动事件是在 以“尽力而为”为原则。最终,Houdini 的合成器 Worklet 是解决此问题的高保真解决方案。

还不够完美

我们目前的 DOM 回收实现并不理想,因为它添加了所有元素, 而不是只关注要传递的 实际显示在屏幕上。也就是说,当您实际快速滚动时, Chrome 需要进行大量布局和绘制工作,以至于无法跟上他们的步伐。您将结束 只能看到背景。这并不是世界末日, 但这绝对是有待改进的地方。

希望您明白,当您希望解决某些简单的问题时, 兼具出色的用户体验和出色的性能标准。包含 渐进式 Web 应用将成为手机上的核心体验,这将 网络开发者将不得不继续向 使用遵循性能限制的模式

所有代码都可以在我们的代码库中找到。我们已经完成了 尽最大努力保持其可重用性,但不会将其作为 npm 或作为单独的代码库。主要用于教育目的。