让 touch 系列事件触发的滚动响应更快 | Web | Google Developers

原文链接:https://developers.google.com/web/updates/2017/01/scrolling-intervention

我们都知道,对于移动端的网页而言,滚动是十分重要的交互,然而 touch 系列事件触发(滚动后)经常会引发严重的性能问题。为了解决这问题,Chrome (通过允许往addEventListener()中传入{passive: true})让touch系列事件的事件监听器变为“被动”(译者注:其实就是touch之后,不再是执行完事件函数后再滚动),同时 pointer
事件
相关的API(也支持相关参数)。这些都是很有用的特性,能使处理( touch 系列)事件的过程中,不会妨碍页面的正常滚动,然而开发者们有时候会觉得它们难以理解,更不会去使用。

我们相信,即使开发者们完全参透浏览器(各种)复杂的细节,也不会使网站性能提高多少。因而在 Chrome 56中,我们将touch系列事件的监听器默认设为“被动”,大多数情况下这正是前端所需要的。我们相信这会极大地提高用户体验,也不会网站造成多大的影响。

在极个别例子下,这会导致意外的滚动。可以通过在(意外滚动发生的)元素上添加CSStouch-action:
none(来阻止滚动发生)。继续阅读,你可以了解到更多相关技术的细节。

背景: 取消默认时事件会降低页面性能

如果你在touchstarttouchmove事件处理函数中调用preventDefault(),这将会阻止(页面)滚动。然而,问题是大多说情况下是不会在事件处理函数中调用preventDefault(),但浏览器需要等到事件处理函数执行完之后才能确定这点。因而开发者可以定义“被动的事件监听器”去解决这问题。当你注册 touch 系列事件的监听器时,加入{passive: true}对象作为第三个参数后,浏览器就认为你不会在事件处理函数中调用 preventDefault(),它就可以安全地让页面滚动,不再等待事件处理函数执行完(再判断)。例子如下:

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

优化

我们主要的目的是为了降低用户触摸屏幕后,内容(滚动)更新的响应时间。为了解 touchstart 和 touchmove 的使用(情况),我们添加了对这两个事件阻止滚动(发生)频率的监控。

我们看到,其中大约80%表现上都是“被动(译者注:也就是并未调用 preventDefault() )”的,但(开发者)却基本没将该事件的监听器注册为“被动”。鉴于此问题的严重性,我们意识到可以通过默认将这些事件(的监听器)设置为“被动”来提高滚动的性能,而且基本不需要任何开发者修改代码。

因而我们做出以下优化:只要 touchstart 或者 touchmove 事件的事件监听器是注册在window, documentbody上的时候,我们会将passive默认为true,也就是说代码是这样的:

1
`window.addEventListener("touchstart", func);`

等同于:

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

现在,在事件处理函数中,调用preventDefault()会变为无效。

下图展示了用户触发滚动后到真正滚动期间,耗时最长的前百分之一案例中所耗费的时间。这些数据是由安卓上的 Chrome 访问任意网页后采集的。在优化( passive )之前,平均耗时是超过400ms,优化后降低到250ms,时间降低了38%。在未来,我们希望默认为所有touchstarttouchmove的事件监听器的passive设置为true,并优化到(滚动响应)低于50ms。

问题与修复

大部分情况下,(我们的优化)不会导致任何 BUG 。但是如果 BUG 真的出现了,最常见问题是当你不希望页面发生滚动时却发生了。极个别的例子是,开发者发现(如果不在touchend事件处理函数中调用preventDefault()click 事件会被触发了。

在 Chrome 56版本及其之后的版本,如果触发了优化,而你又在事件处理函数中调用了preventDefault(),在 DevTools 中会打印一条警告(作为提示)。

1
`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`

你可以直接在应用中,通过defaultPrevented属性检查preventDefault的调用是否生效。

我们发现大量受影响的页面,通过使用touch-action这个CSS属性就能轻松修复(问题)。如果你希望某个元素(无论进行任何 touch 操作都)不会使浏览器发生滚动或缩放,可以往该元素的CSS中加入touch-action: none。如果想某个元素只可以水平滚动或缩放,可以使用touch-action: pan-y pinch-zoom。在浏览器中正确地使用 touch-action 是已经是非常重要了,如 PC 端中 Edge 支持 Pointer 事件,不支持 Touch 事件。可惜的是,移动端 Safari 和一些旧的浏览器不支持 touch-action,因此你仍然需要在事件处理函数中调用preventDefault,尽管 Chrome 会忽略它。

在更复杂的例子中,你可能需要参考下面的其中一条(来解决问题):

  • 如果你的touchstart事件的监听器中,调用了preventDefault(),为阻止触发click事件和浏览器的默认行为,请确保preventDefault()在相关的touchend事件的监听器中也被调用。

  • 如果需要往addEventListener()中传入{passive: false}来覆盖浏览器默认行为,请确保该浏览器支持EventListenerOptions属性。

小结

开发者通常只会察觉到,通过优化后的 Chrome 56中访问大多数网页时,滚动响应会更快。而在个别的例子中,开发者可能会发现一些意外的滚动。

虽然仍需要为移动端的 Safari 调用preventDefault(),然而 Chrome 已经不再推荐网站依靠在touchstarttouchmove 事件处理函数中调用preventDefault()(来阻止浏览器默认行为)。开发者在需要时,应该在 touch 系列事件发生前,使用touch-action这一 CSS 属性去阻止某元素滚动或缩放。只有在为了阻止之后的默认行为(如将要触发的click事件)时,才应该在touchend的事件处理函数中调用preventDefault()

除非另有声明,此文章采用Creative Commons Attribution 3.0 License许可,示例代码采用Apache 2.0 License许可。如需了解更多,请查阅我们的网站政策