关于passive event listener的一次踩坑 你真的会用addEventListener吗?
没错,的确很标题党,但是我最近发现我的一个朋友(谁说是我的!)真的不太会用addEventListener
。
起因 我最近参与了一个开源项目,vue-carousel ,Vue生态圈里的一个轮播组件,由加拿大的一个电商公司SSENSE开源的。然后刚好有人提了一个issue
Unable to preventDefault inside passive event listener due to target being treated as passive
也许有人在开发移动端应用的时候遇到过这个问题,我来为大家复现一下这个场景:
当我们给document
添加了touch
事件的监听器的时候,如果同时在handler内部调用了event.preventDefault()
,这时候浏览器(Chrome 56+)就会报一个warning:Unable to preventDefault inside passive event listener due to target being treated as passive
这句警告翻译过来就是:不能给passive(被动的)事件监听器preventDefault
,因为它被认为是passive。
什么意思呢?不着急,首先,我们来看一下什么叫passive event listener
。但是在这之前,我们还是得复习一下addEventListener
的第三个参数。
不为所知的第三个参数 当我们使用addEventListener
的时候,我们一般的写法是以下:
target.addEventListener(event, handler)
复制代码
相信还有人使用更完整的写法:
target.addEventListener(event, handler, false )
复制代码
是的,相信很多人也已经知道了,addEventListener
方法是有第三个参数的,我们时常传入一个false
来作为这第三个参数。但是我们不传其实也一样,因为这第三个参数默认就是false
,是不是觉得自己不明所以地写了很多冤枉代码还自以为很严谨?
哈哈,所以我们得来看看,这第三个参数到底是做什么的。
常规操作下,这第三个参数是一个布尔值,叫useCapture
,也就是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件目标,接收到该事件。
我们知道,DOM事件流(event flow)存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。如果useCapture
设置为false
,当前eventTarget就不会在捕获阶段接收该事件。浏览器默认我们不会在捕获阶段触发绑定事件的handler。
但是我相信还是有很多人没有认真看过addEventListener
的文档,第三个参数其实并不一定是一个布尔值。他也可以是一个对象,一组配置。
{
capture: Boolean , // 表示`listener`会在该类型的事件捕获阶段传播到该`EventTarget`时触发
once: Boolean , // 表示`listener`在添加之后最多只调用一次。如果是`true`,`listener`会在其被调用之后自动移除
passive: Boolean , // 表示`listener`永远不会调用`preventDefault()`。如果`listener`仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告
}
复制代码
除以上之外,还有一个mozSystemGroup
,我们暂时不讨论。
所以其实我们还可以在useCapture
的基础上另外配置两个配置项。
once
表示listener
在添加之后最多只调用一次。如果是true
,listener
会在其被调用之后自动移除,这跟我们在jQuery时代的once
方法比较像。
passive
表示listener
永远不会调用preventDefault()
。如果listener
仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。具体的我们接着讨论。
passive event listener 在具体讨论passive event listener
之前,我们先普及一个知识点 。大家可以自己去看看英文文档,不过需要科学上网。
简而言之就是当我们在滚动页面的时候(通常是我们监听touch事件的时候),页面其实会有一个短暂的停顿(大概200ms),浏览器不知道我们是否要preventDefault
,所以它需要一个延迟来检测。这就导致了我们的滑动显得比较卡顿。
从Chrome 51开始,passive event listener
被引进了Chrome,我们可以通过对addEventListener
的第三个参数设置{ passive: true }
来避免浏览器检测这个我们是否有在touch事件的handler里调用preventDefault
。在这个时候,如果我们依然调用了preventDefault
,就会在控制台打印一个警告。告诉我们这个preventDefault
会被忽略。
当我们给addEventListener
的第三个参数设置了{ passive: true }
,这个事件监听器就被称为passive event listener 。
从Chrome 56开始,如果我们给document
绑定touchmove
或者touchstart
事件的监听器,这个passive
是会被默认设置为true
以提高性能,具体chromestatue 文档 。但是我们大多数人并不知道这点,并且依旧调用了preventDefault
。这并不会导致什么页面崩溃级的错误,但是这可能导致我们忽略了一个页面性能优化的点,特别是在移动端这种更加重视性能优化的场景下。
兼容性 第三个参数是在近段时间才被调整为一组配置项,如果我们需要兼容旧版浏览器,我们需要写一些检测代码。
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
的参数。
PS: 在vue-carousel的那个bug的最开始的PR里,我将addEventListener
的passive
设置为了false
,但是这其实不是最优解,最优解应该是去掉监听器里的preventDefault
,我已经提了另一个PR来解决这个问题。
参考:
来自 https://juejin.im/post/5ad804c1f265da504547fe68
滑动时候警告:Unable to preventDefault inside passive event listener
2
2018.04.09 22:01:15 字数 528 阅读 108,208
移动端Web界面滚动性能优化 Passive event listeners 最近更新了ios11.3,项目上发现这么一个问题,“我的”页面和两个列表页的滚动出现了问题,滚动时候不仅滚动了希望滚动的部分,整体的页面也跟随者上下滚动,整个页面非常卡顿。
这两个页面都用了touch事件
控制台打印如下警告:
[ Intervention ] Unable to preventDefault inside passive event listener due to target being treated as passive.
See https: / / www. chromestatus. com/ features/ 5093566007214080
解决办法1: 在touch的事件监听方法上绑定第三个参数{ passive: false }, 通过传递 passive 为 false 来明确告诉浏览器:事件处理程序调用 preventDefault 来阻止默认滑动行为。
elem.addEventListener(
'touchstart',
fn,
{ passive: false }
);
解决办法2: * { touch-action : pan-y; }
使用全局样式样式去掉
Passive event listeners 2016年Google I/O上提出的概念,目的是用来提升页面滑动的流畅度。
For instance, in Chrome for Android 80% of the touch events that block scrolling never actually prevent it. 10% of these events add more than 100ms of delay to the start of scrolling, and a catastrophic delay of at least 500ms occurs in 1% of scrolls.
在 Android 版 Chrome 浏览器的 touch 事件监听器的页面中,80% 的页面都不会调用 preventDefault 函数来阻止事件的默认行为。在滑动流畅度上,有 10% 的页面增加至少 100ms 的延迟,1% 的页面甚至增加 500ms 以上的延迟。
由于浏览器无法预先知道一个事件处理函数中会不会调用 preventDefault(),它需要等到事件处理函数执行完后,才能去执行默认行为,然而事件处理函数执行是要耗时的,这样一来就会导致页面卡顿,也就是说,当浏览器等待执行事件的默认行为时,大部分情况是白等了。
如果 Web 开发者能够提前告诉浏览器:“我不调用 preventDefault 函数来阻止事件事件行为”,那么浏览器就能快速生成事件,从而提升页面性能,Passive event listeners 的提出就解决了这样的问题。
来自 https://www.jianshu.com/p/04bf173826aa
Unable to preventDefault inside passive event listener
最近做项目经常在 chrome 的控制台看到如下提示:
Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080
于是 Google 了一番,找到这篇文章,有了详细解释。Making touch scrolling fast by default
简而言之:
由于浏览器必须要在执行事件处理函数之后,才能知道有没有掉用过 preventDefault() ,这就导致了浏览器不能及时响应滚动,略有延迟。
所以为了让页面滚动的效果如丝般顺滑,从 chrome56 开始,在 window、document 和 body 上注册的 touchstart 和 touchmove 事件处理函数,会默认为是 passive: true。浏览器忽略 preventDefault() 就可以第一时间滚动了。
wnidow.addEventListener('touchmove', func) 效果和下面一句一样
wnidow.addEventListener('touchmove', func, { passive: true })
这就导致了一个问题:
如果在以上这 3 个元素的 touchstart 和 touchmove 事件处理函数中调用 e.preventDefault() ,会被浏览器忽略掉,并不会阻止默认行为。
测试:
background: linear-gradient(to bottom, red, green);
// 在 chrome56 中,照样滚动,而且控制台会有提示,blablabla
window.addEventListener('touchmove', e => e.preventDefault())
那么如何解决这个问题呢?不让控制台提示,而且 preventDefault() 有效果呢? 两个方案: 1、注册处理函数时,用如下方式,明确声明为不是被动的 window.addEventListener('touchmove', func, { passive: false })
2、应用 CSS 属性 touch-action: none;
这样任何触摸事件都不会产生默认行为,但是 touch 事件照样触发。 touch-action 还有很多选项,详细请参考touch-action
[注]未来可能所有的元素的 touchstart touchmove 事件处理函数都会默认为 passive: true
来自 https://blog.csdn.net/csdnXiaoZou/article/details/87276026