你不知道的passive event listener-让移动端滑动体验起飞(优化页面滑动)
优化前和优化后的对比
事情的起因是在腾讯面试的时候被问到了,被吊打了,想想也不冤,以前确实从来没碰到过这样的问题,如果以前碰到过还回答不出来,我就去撞墙了
先来个场景:
<script type="text/javascript">
document.addEventListener("touchstart", function(e){
e.preventDefault()
})
</script>
当你去测试时:
报错了:Unable to preventDefault inside passive event listener due to target being treated as passive
翻译过来就是: 由于目标被视为被动的,无法在被动事件监听器中阻止默认设置。
此时引出一个词:passive event listener
说这个问题之前,先复习一下addEventListener
的第三个参数,你真的足够了解吗?
addEventListener 鲜为人知的第三个参数:
当我们使用addEventListener的时候,我们一般的写法是以下:
target.addEventListener(event, handler)
可能完整些写是这样
target.addEventListener(event, handler, false)
一般情况下,这第三个参数是一个布尔值,叫useCapture,是否使用事件捕获的意思,DOM事件流(event flow)存在三个阶段:事件捕获阶段
、处于目标阶段
、事件冒泡阶段
。
如果useCapture设置为false
,当前eventTarget就不会在捕获阶段接收该事件。浏览器默认我们不会在捕获阶段触发绑定事件的handler。
那么问题来了,第三个参数就这?
翻开MDN文档addEventListener
target.addEventListener(type, listener, options);
target.addEventListener(type, listener, useCapture);
原来第三个参数除了可以是一个boolean类型外,还可以是一个options配置对象!!
{
capture: Boolean, // 表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发
once: Boolean, // 表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
passive: Boolean, // 设置为true时,表示`listener`永远不会调用`preventDefault()`。如果`listener`仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告
}
那知道这个有什么用呢?再来看一个知识点
当我们在滚动页面的时候(通常是我们监听touch事件的时候),页面其实会有一个短暂的停顿(大概200ms),浏览器不知道我们是否要preventDefault,所以它需要一个延迟来检测。这就导致了我们的滑动显得比较卡顿。
从Chrome 51开始,passive event listener
被引进了Chrome,我们可以通过对addEventListener
的第三个参数设置{ passive: true }
来避免浏览器检测这个我们是否有在touch
事件的handler
里调用preventDefault
。在这个时候,如果我们依然调用了preventDefault
,就会在控制台打印一个警告或者报错
当我们给addEventListener
的第三个参数设置了{ passive: true }
,这个事件监听器就被称为passive event listener。
划重点:
从Chrome 56开始,如果我们给document
绑定touchmove
或者touchstart
事件的监听器,这个passive是会被默认设置为true以提高性能
,但是我们大多数人并不知道这点,并且依旧调用了preventDefault,所以就出现了文章开始时那个demo的报错,这并不会导致什么页面崩溃级的错误,但是这可能导致我们忽略了一个页面性能优化的点,特别是在移动端这种更加重视性能优化的场景下。
passive event listener的应用:
仔细看文档其实也有说
,使用passive改善的滚屏性能
添加passive参数后,touchmove事件不会阻塞页面的滚动(同样适用于鼠标的滚轮事件)
你可以这么理解: 当你触摸滑动页面时,页面应该跟随手指一起滚动。而此时你绑定了一个 touchmove
事件,你的事件大概执行 200 毫秒。这时浏览器就犯迷糊了:如果你在事件绑定函数中调用了 preventDefault,那么页面就不应该滚动
,如果你没有调用 preventDefault,页面就需要滚动
。但是你到底调用了还是没有调用,浏览器不知道。只能先执行你的函数,等 200 毫秒后,绑定事件执行完了,浏览器才知道,“哦,原来你没有阻止默认行为,好的,我马上滚”。此时,页面开始滚。
document.addEventListener("touchmove", function(e){
//做些东西
},{passive:true})
所以设置{ passive: true }
的意义是
告诉浏览器立马滚动,不用等200毫秒后确认了
,我根本就没有preventDefault
,此时你滚动起来再也没有延迟感了,舒服了
兼容性问题:
其实文档也有说啦
,option支持的安全检测
注意:那些不支持参数options的浏览器,会把第三个参数默认为useCapture,即设置useCapture为true
因为旧版本的浏览器(以及一些相对不算古老的)仍然假定第三个参数是布尔值,你需要编写一些代码来有效地处理这种情况。你可以对每一个你感兴趣的options值进行特性检测。
如果你想检测 passive 值可以参考下面这个例子:
var passiveSupported = false;
try {
var options = Object.defineProperty({}, "passive", {
get: function() {
passiveSupported = true;
}
});
window.addEventListener("test", null, options);
} catch(err) {}
这段代码为passive
属性创建了一个带有getter
函数的options
对象;getter
设定了一个标识,passiveSupported
,被调用后就会把其设为true
。那意味着如果浏览器检查options
对象上的passive
值时,passiveSupported
将会被设置为true
;否则它将保持false
。然后我们调用addEventListener()
去设置一个指定这些选项的空事件处理器,这样如果浏览器将第三个参数认定为对象的话,这些选项值就会被检查。
你可以利用这个方法检查options之中任一个值。只需使用与上面类似的代码,为选项设定一个getter。
然后,当你想实际创建一个是否支持options的事件侦听器时,你可以这样做:
someElement.addEventListener("mouseup", handleMouseUp, passiveSupported
? { passive: true } : false);
我们在 someElement
这里添加了一个mouseup
。对于第三个参数,如果 passiveSupported
是 true
,我们传递了一个 passive
值为 true
的 options
对象;如果相反的话,我们知道要传递一个布尔值,于是就传递 false
作为 useCapture
的参数
完整代码如下:
var supportsPassive = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function() {
supportsPassive = true;
}
});
window.addEventListener("test", null, opts);
} catch (e) {}
//supportsPassive出结果后,使用它来进行判断
elem.addEventListener(
'touchmove',
fn,
supportsPassive ? { passive: true } : false
);
相关文章
- POJ2308连连看dfs+bfs+优化
- rabbitmq之队列性能测试及优化方法(六)
- 如何将WebAssembly优化到1MB?
- Flutter项目实战教程分享、基础使用、性能优化、每日积累
- Android merge优化UI
- 【前端性能】高性能滚动 scroll 及页面渲染优化
- Python 代码性能优化技巧
- 【MATLAB教程案例97】基于GA遗传优化的CNN卷积神经网络最优训练参数搜索matlab仿真
- 你要为难优化器,优化器会加倍为难你
- 页面优化,谈谈重绘(repaint)和回流(reflow)
- vue-router和webpack懒加载,页面性能优化篇
- 《Oracle性能优化与诊断案例精选》——第2章 回首向来萧瑟处,也无风雨也无晴
- Android性能优化之页面优化
- 使用BatteryHistorian分析和优化应用电量
- 【转载】 Android ListView性能优化之视图缓存
- mysql性能优化-慢查询分析、优化索引和配置
- vue-cli + webpack 多页面实例配置优化方法
- 全文检索 (不包含、不等于) 索引优化 - 阿里云RDS PostgreSQL最佳实践
- 【bzoj4016】[FJOI2014]最短路径树问题 堆优化Dijkstra+DFS树+树的点分治
- Redis--缓存设计与性能优化