您是否曾经在 Facebook 或 Twitter 上愉快地向下滚动页面,突然浏览器似乎卡住了?它会卡在那里好几秒钟,您不确定它是否会崩溃。然后,终于,页面突然跳动以追赶您的滚动,让您感到迷茫?这就是所谓的卡顿,在 Firefox 46 Beta 版中,我们正在努力使卡顿成为过去。
在 Firefox 46 Beta 版中,我们正在为 Firefox 引入异步平移和缩放 (APZ)。这项底层技术最初是为我们的移动平台开发的,在移动平台上,对触摸输入的响应能力是性能支柱之一。现在,我们将它移植到我们的桌面平台,并将其用于鼠标滚轮/触控板滚动。使用 APZ,即使页面正在运行大量 JavaScript 或重绘速度很慢,向下滚动页面也应该流畅且无卡顿。在未来的版本中,我们计划将 APZ 扩展到其他滚动方法(例如触控和滚动条拖动)。
幕后
浏览器传统上在浏览器的主要事件处理线程上处理用户输入事件。这意味着它可能会被浏览器正在执行的任何其他操作阻塞,包括运行脚本和绘制。在 Firefox 中,电解 (e10s) 项目将内容进程与主浏览器进程分离。这为我们提供了另一种处理输入事件的路径。与以往将输入事件从父进程传递到内容进程以驱动滚动不同,输入事件用于在父进程合成器中驱动滚动,然后才转发到子进程以进行正常处理和分派。下图说明了这一点。
在上面的图 1 中,潜在的长时间运行操作用红色表示。虽然合成器线程仍然可以快速合成到屏幕上(每秒 60 帧),但它依赖于从主线程获取绘制内容。由于主线程可能会被长时间运行的 JavaScript 或绘制操作阻塞,因此来自滚动输入的绘制更新也会被阻塞,无法到达合成器。
在上面的图 2 中,我们可以看到引入 e10s 和 APZ 如何影响流程。输入事件到达父进程的主线程,该线程没有长时间运行的操作。因此,它可以快速将输入事件传递到合成器线程,APZ 代码在其中使用它进行异步滚动。同时,输入事件也被转发到子进程的主线程,在那里它被传递到网页内容并触发重绘。即使这些操作可能仍然很慢,它们也不会再阻止滚动更新合成到屏幕上。
这种方法虽然对响应能力很有帮助,但需要启用 e10s,并且还会引入一定程度的复杂性。例如,父进程需要能够对子进程进行一定程度的命中测试,以了解输入事件落在哪里。它还意味着父进程需要一种机制来处理网页调用了preventDefault()
的事件。有关所有这些工作原理的详细信息,请参阅APZ 文档。
棋盘格
当然,没有什么是免费的,APZ 也需要付出代价。APZ 确实消除了卡顿,但在某些情况下,它是通过棋盘格来实现的。棋盘格是指您滚动速度超过浏览器绘制页面的速度时会出现的情况。当这种情况发生时,您新滚动位置的内容还没有被绘制出来,因此我们只显示一个纯色背景。(术语棋盘格来自最初的 iPhone 实现,它会显示棋盘格图案。)一旦绘制赶上来,内容就会填充进去。
由于内存限制,异步滚动不可避免地会出现棋盘格现象;这是消除卡顿的代价。但是,在大多数情况下,它应该不明显 - 它只持续几百毫秒,我们正在努力进一步缩短这个时间。我们通过提前绘制可见区域之外的内容来做到这一点,这样当用户滚动时,内容就可以立即显示。通过更好地预测用户将要滚动的位置,我们可以减少棋盘格的显示数量。总的来说,用棋盘格来换取卡顿可以带来更好的用户体验,因为浏览器本身保持响应,不会出现卡住或挂起。
与滚动相关的效果
APZ 还会影响与滚动相关的效果的工作方式。由于 APZ 与主线程异步地进行滚动,这必然意味着网页内容看到的滚动事件将会延迟。换句话说,从用户滚动到滚动事件可以分派到网页内容之间存在一定的传播延迟。如果页面使用滚动事件在页面上重新定位一些内容,则处理该更改并将其显示给用户将需要额外的时间。这意味着与滚动相关的效果可能看起来延迟且与滚动不同步。在理想情况下,用户滚动和与滚动相关的效果可见之间的延迟不应超过 2 帧(在每秒 60 帧的帧速率下为 33 毫秒)。虽然这几乎不明显,但实际上如果页面使用脚本占用了主线程,或者页面很复杂,需要一段时间才能重绘,则可能需要更长的时间。因此,页面仍然需要尽可能保持响应,方法是将长时间运行的脚本分解或使用 worker。
为了完全消除这种延迟,作者可以选择使用 CSS 定位而不是 JavaScript 来实现与滚动相关的效果。我们为最常用的与滚动相关的效果提供了 CSS 选项,包括粘性定位和视差效果。使用 CSS 定位,我们可以提前知道定位将如何变化,因此我们可以使其与用户的滚动完全同步。您可以在 MDN 文章中找到有关与滚动相关的效果的示例和更多详细信息。
关于 Kartikaya Gupta
Kartikaya Gupta 是 Mozilla 平台图形团队的开发人员,负责 Gecko 渲染引擎中的滚动相关事项。
15 条评论