在默认情况下实现快速轻触滚动

Dave Tapuska
Dave Tapuska

我们知道,滚动响应能力对用户与移动网站的互动至关重要,但触摸事件监听器通常会导致严重的滚动性能问题。Chrome 一直在通过允许触摸事件监听器为被动(将 {passive: true} 选项传递给 addEventListener())并提供指针事件 API 来解决此问题。这些功能非常适合将新内容引入到不会阻止滚动的模型中,但开发者有时会发现它们难以理解和采用。

我们认为,Web 应该默认快速,开发者无需了解浏览器行为的深奥细节。在 Chrome 56 中,如果在大多数情况下,被动触摸监听器与开发者的意图相符,我们会默认将触摸监听器设为被动。我们相信,通过这样做,我们可以大幅提升用户体验,同时对网站的影响降到最低。

在极少数情况下,此更改可能会导致意外滚动。通常,只需将 touch-action: none 样式应用于不应发生滚动的元素,即可轻松解决此问题。请继续阅读,详细了解相关信息、如何确定您是否受到影响,以及您可以采取哪些措施。

背景知识:可取消的事件会降低网页速度

如果您在 touchstart 或第一个 touchmove 事件中调用 preventDefault(),则会阻止滚动。问题在于,大多数情况下监听器不会调用 preventDefault(),但浏览器需要等待事件完成才能确定这一点。开发者定义的“被动事件监听器”可解决此问题。当您在事件处理程序中添加触摸事件并将 {passive: true} 对象作为第三个参数时,您将告知浏览器 touchstart 监听器不会调用 preventDefault(),并且浏览器可以安全地执行滚动,而不会阻塞监听器。例如:

window.addEventListener("touchstart", func, {passive: true} );

干预

我们的主要目标是缩短用户触摸屏幕后更新显示内容所需的时间。为了了解 touchstart 和 touchmove 的使用情况,我们添加了一些指标来确定滚动阻塞行为的发生频率。

我们查看了发送到根目标(窗口、文档或正文)的可取消触摸事件的百分比,并确定其中约 80% 的监听器在概念上是被动的,但并未以这种方式注册。鉴于此问题的规模,我们发现了一个绝佳的机会,可以通过将这些事件自动设为“被动”来改善滚动,而无需开发者执行任何操作。

因此,我们将干预定义为:如果 touchstart 或 touchmove 监听器的目标是 windowdocumentbody,我们将 passive 默认为 true。这意味着,以下代码

window.addEventListener("touchstart", func);

等效于:

window.addEventListener("touchstart", func, {passive: true} );

现在,系统会忽略监听器内对 preventDefault() 的调用。

下图显示了前 1% 滚动操作所需的时间,从用户轻触屏幕开始滚动到显示屏更新的时间。这些数据适用于 Android 版 Chrome 中的所有网站。在启用干预措施之前,1% 的滚动操作只需要 400 毫秒多一点的时间。现在,在 Chrome 56 Beta 版中,该时间已缩短至 250 毫秒左右,缩短了约 38%。未来,我们希望将“passive true”设为 所有 touchstarttouchmove 监听器的默认值,将此时间缩短到 50 毫秒以下。

“前 1% 的滚动时间”图表

中断和指南

在大多数情况下,不会出现任何中断。但是,如果确实发生了断点,最常见的症状是,在您不希望滚动时发生滚动。在极少数情况下,开发者可能还会注意到意外的点击事件(当 touchend 监听器中缺少 preventDefault() 时)。

在 Chrome 56 及更高版本中,当您在干预处于有效状态的事件中调用 preventDefault() 时,DevTools 会记录一条警告。

touch-passive.html:19 Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

您的应用可以通过检查调用 preventDefault 是否通过 defaultPrevented 属性产生了任何影响,来确定它是否可能会在实际环境中遇到此问题。

我们发现,只要尽可能应用 touch-action CSS 属性,就可以相对轻松地修复大多数受影响的网页。如果您想阻止在元素内进行所有浏览器滚动和缩放,请对其应用 touch-action: none。如果您使用的是横向轮播界面,不妨考虑对其应用 touch-action: pan-y pinch-zoom,以便用户仍能照常垂直滚动和缩放。在桌面版 Edge 等支持指针事件而非触摸事件的浏览器中,正确应用 touch-action 已是必不可少的。对于不支持 touch-action 的移动 Safari 和旧版移动浏览器,您的触摸监听器必须继续调用 preventDefault,即使 Chrome 会忽略它也是如此。

在更复杂的情况下,可能还需要依赖以下任一方法:

  • 如果您的 touchstart 监听器调用 preventDefault(),请确保从关联的 touchend 监听器中也调用 preventDefault(),以继续抑制生成点击事件和其他默认点按行为。
  • 最后(不建议)将 {passive: false} 传递给 addEventListener() 以替换默认行为。请注意,您必须进行功能检测,以确定用户代理是否支持 EventListenerOptions

总结

在 Chrome 56 中,许多网站的滚动启动速度明显更快。这是大多数开发者会注意到的唯一影响。在某些情况下,开发者可能会注意到意外滚动。

虽然对于移动 Safari 仍需要这样做,但网站不应依赖于在 touchstarttouchmove 监听器中调用 preventDefault(),因为 Chrome 不再保证会执行此操作。开发者应在应停用滚动和缩放的元素上应用 touch-action CSS 属性,以便在发生任何触摸事件之前通知浏览器。如需抑制点按的默认行为(例如生成点击事件),请在 touchend 监听器内调用 preventDefault()