原文: https://developers.google.com/web/updates/2017/01/scrolling-intervention
我们知道滚动灵敏度对于手机端使用web站点的用户沉浸度至关重要,但touch事件监听器经常会引起严重的滚动性能问题.Chrome已经通过允许touch事件监听器变为passive(被动的)(将{passive: true}
传递给addEventListener()
)的方式和严格管理pointer事件 API来处理这种问题了.这是一些很赞的特性,让新的内容进入模型当中,并且不阻塞住滑动,但是开发者们有些时候发现这很难理解和采纳.
我们相信,网页应该自然而然的很快,而不是需要开发者理解晦涩难懂的浏览器行为细节.在Chrome 56中,我们将touch监听器默认设为passive,为的就是吸引开发者们的注意.我们相信通过这么做,能够很大程度上提升用户体验,并且尽量缩减站点上的负面体验.
在极个别的场景下,这项调整可能引起预料之外的滚动.这通常可以简单的通过应用touch-action: none
样式到那些不该出现滚动的元素上.继续阅读来了解更多细节,比如怎样知道你是否被影响到了;假如是,你该怎么做.
背景: 可取消的事件让你的页面变得更慢
![](https://img.haomeiwen.com/i56687/744455baa78cbb8d.png)
如果在touchstart
或首次touchmove
事件中调用了preventDefault()
方法,那么你将会阻止滚动事件.问题是大部分监听器不会去调用preventDefault()
方法,但是浏览器需要等待该事件结束来确保它会不会调用.针对开发者定义的"passive event listeners"解决了这个问题.当你向一个touch事件添加了{passive: true}
对象作为事件处理器的第三个参数时,代表着你告诉浏览器这个touchstart
事件将不会调用preventDefault()
,然后浏览器就会以不阻塞的方式安全地进行滚动了.例如:
window.addEventListener("touchstart", func, {passive: true} );
干涉
我们的主要动机是降低用户触摸屏幕之后更新展示内容的时间.为了理解touchstart和touchmove的使用,我们增加了度量数据来弄清滚动阻塞行为出现的有多频繁.
我们查看了被发送到根目标(window,document,或body)的可取消触摸事件的百分比,并且弄清了大概80%的事件监听器理论上是被动的,但是我们没有刻意将它们变为这样.考虑到这个问题的规模,我们意识到通过让这些事件自动变成"passive"而不是通过开发者,将会有巨大的机会来提高滚动性能.
这驱使我们定义了如下的干涉规则: 如果touchstart或touchmove监听器的目标是window
,document
或body
,我们默认将passive
设为true
.这意味着这样的代码:
window.addEventListener("touchstart", func);
变得等价于:
window.addEventListener("touchstart", func, {passive: true} );
现在在监听器内部调用preventDefault()
将会被忽略.
下面的图标展示了出现频率最高的1%的滚动操作所花费的时间, 该时间代表一个用户从触摸屏幕滑动到展示更新完成.这份数据针对于所有在安卓Chrome中运行的网页.在这项干涉开启之前,1%的滚动操作花费略高于400ms.但是现在在Chrome 56 Beta中已经下降为略高于250ms;这个降幅大概有38%.未来我们希望让所有的touchstart
和touchmove
监听器默认都是被动的,这样能将该时间降低至50ms.
![](https://img.haomeiwen.com/i56687/8b0170c8c25146b6.png)
破坏性和指导意见
在大量的用例中,并没有观测到破坏性行为.但是当意外来临的时候,最常见的现象表现为当你不想滚动的时候,反而滚动了.在很少的案例中,开发者同样也发现了一些预料之外的click
事件(当preventDefault()
在touch
监听器中缺失的时候).
在Chrome 56和之后的版本,DevTools将会输出一个警告,比如当干涉生效的时候你在事件中调用了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
你的应用能够通过某种检查来断定它是否在某些地方会踩到这个雷.这种检查通过defaultPrevented
属性来判断调用preventDefault
是否有任何的影响.
我们已经发现了大量受影响的页面可以通过touch-actionCSS属性来修复.如果你想要在元素上阻止所有的浏览器滚动和缩放操作,那么加一个touch-action: none
.如果你有一个水平的跑马灯并考虑在上面应用touch-action: pan-y pinch-zoom
,然后用户就能够像往常一样在垂直方向上进行滚动和缩放.在支持Pointer Events和非Touch Events的浏览器上,比如桌面版的Edge上正确的应用touch-action已然变得非常必要了.对移动端的Safari和不支持touch-action的老版移动浏览器,你的touch监听器必须继续调用preventDefault
方法,即使该方法已经被Chrome忽略了.
在更复杂的案例中,可能要依赖下面的方法:
-
如果
touchstart
监听器调用了preventDefault()
,确保在相对应的touchend监听器中也同样调用了preventDefault()
方法,从而确保能够阻止click事件和其它默认的点击行为的产生. -
最次的(也是不建议的)方法是显式的将
{passive: false}
传入到addEventListener()中来覆盖默认的浏览器行为.需要注意的是如果浏览器支持EventListenerOptions,那么你需要做特性检测.
总结
在Chrome 56版本中,在很多网站上的滚动性能已经有了较为可观的提升.最大的影响就是大多数的开发者将会意识到这项改变的结果.在一些案例中,开发者可能会注意到那些非预期的滚动.
尽管对于移动端的Safari来说这仍然很有必要,网站不应该依赖在touchstart
和touchmove
监听器上调用preventDefault()
来阻止默认行为.因为在Chrome中,这样做已经不被支持和提倡了.开发者应该在那些需要禁用滚动和缩放的元素上增加touch-action
CSS属性,好在任何touch事件出现前通知到浏览器.为了阻止tap(就像是一个click事件的产生)的默认行为,在touchend
监听器中调用preventDefault()
.
网友评论