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

戴夫·塔普斯卡
Dave Tapuska

我们知道,滚动响应对于用户与移动设备上的网站互动至关重要,但触摸事件监听器经常会导致严重的滚动性能问题。Chrome 一直希望解决此问题,方法是允许触摸事件监听器处于被动状态(将 {passive: true} 选项传递给 addEventListener())并推出指针事件 API。这些实用的功能可以将新内容推送到不会阻止滚动的模型中,但开发者有时会发现难以理解和采用它们。

我们认为,网络应默认呈现速度快,而开发者无需了解浏览器行为的神秘细节。在 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% 滚动操作所用的时间。此数据针对 Chrome(Android 版)中的所有网站。在启用干预之前,1% 的滚动只需超过 400 毫秒。现在,在 Chrome 56 Beta 版中,该时间缩短至略高于 250 毫秒,减少了约 38%。未来,我们希望将所有 touchstarttouchmove 监听器的默认设置为被动 true,从而将其缩短到 50 毫秒以下。

前 1% 的滚动时间图表

中断和指导

在绝大多数情况下,不会观察到服务中断。但是,当确实发生损坏时,最常见的症状是在不需要时滚动。在极少数情况下,开发者还可能注意到意外的 click 事件(当 touchend 监听器缺少 preventDefault() 时)。

在 Chrome 56 及更高版本中,如果您在干预处于活跃状态的情况下调用 preventDefault(),开发者工具会记录一条警告。

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)上,必须正确应用触摸操作。对于移动 Safari 以及不支持触摸操作的旧版移动浏览器,即使 Chrome 忽略了您的触摸监听器,它也必须继续调用 preventDefault

在更复杂的情况下,您可能还需要依赖于以下某一项:

  • 如果您的 touchstart 监听器调用 preventDefault(),请确保也会从关联的触摸监听器监听器中调用 preventDefault(),以便继续禁止生成点击事件和其他默认点按行为。
  • 最后(不建议这样做)向 addEventListener() 传递 {passive: false} 以替换默认行为。请注意,您必须提供检测用户代理是否支持 EventListenerOptions 的功能。

总结

在 Chrome 56 中,在许多网站上开始滚动的速度要快得多。由于此更改,大多数开发者只会注意到这一影响。在某些情况下,开发者可能会注意到意外滚动。

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