起源
最近打开项目随便点点,控制台就开始报警:
Added non-passive event listener to a scroll-blocking ‘mousewheel’ event. Consider marking event handler as ‘passive’ to make the page more responsive
可以看到警告信息是element-ui和echarts两个库发出来的, 原因是使用了mousewheel和wheel两个事件,但是却没有传递passive参数。
什么是passive参数?
passive adj.消极的,被动的
打开MDN,搜索addEventListener可以看到,addEventListener传递的第三个参数option中有passive选项,它设置为true时表示不会调用preventDefault()方法。
那么问题来了:一个事件是否调用preventDefault()对浏览器有什么影响嘛?
preventDefault与浏览器
对于一个滚动事件mousewheel(非标准)/touch(移动端事件)/wheel(标准)来说,浏览器需要先去调用他的回调函数,然后再去执行默认行为(在这里是滚动)。然而执行回调函数是需要时间的,如果回调函数中放了一个耗时的循环,它就会等待这个循环执行完再去滚动,那么页面表现就是滑动滚轮后的一段时间页面才会滚动,会有很明显的延迟感。
除此之外,由于执行回调函数需要浏览器的主线程,因此当主线程忙着执行其他任务时就会来不及执行回调函数,页面同样会出现延迟感。
一个小实验——耗时的回调函数
我们让一个页面的滚动事件里执行一段耗时的程序,可以很明显的看出效果
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><li>123</li><!-- 省略100个li,vscode输入li{123}*100即可快速生成 --><script>function fib(n) {if (n == 1 || n == 2) return n;return fib(n - 2) + fib(n - 1);}window.addEventListener("wheel",async (e) => {console.log(fib(40));},{ passive: false });</script></body>
</html>
当页面滚动时,求斐波那契数列,经过我的多次测试,我的电脑将n设置为40效果比较好,n太低了算的太快效果看不出来,太高了页面直接卡死崩溃了。
滑动滚轮后,页面无反应,等过了约1s后页面才开始滚动,并且控制台出现如下信息:
Handling of 'wheel' input event was delayed for 1450 ms due to main thread being busy. Consider marking event handler as 'passive' to make the page more responsive.
由于主线程繁忙,'wheel'输入事件的处理延迟了1450毫秒。 考虑将事件处理程序标记为“passive”,以使页面响应更快。

可以看到浏览器发现了这个wheel事件的回调函数时间太长了,推荐开发者设置passive属性,那么我们将passive设置为true试一下
将passive设置为true后,虽然控制台依然会有很明显的延迟输出,但是滑动滚轮时页面很流畅,并不卡顿
一个小实验——繁忙的主线程
这次我们给页面添加一个计算按钮,给按钮注册一个点击后计算斐波那契数据列的事件,当点击按钮后立刻滑动滚轮查看页面表现
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><button>calc</button><li>123</li><!-- 省略100个li,vscode输入li{123}*100即可快速生成 --><script>const btn = document.querySelector('button')btn.onclick = function(){fib(41)}function fib(n) {if (n == 1 || n == 2) return n;return fib(n - 2) + fib(n - 1);}window.addEventListener("wheel",async (e) => {console.log('wheel');},{ passive: false });</script></body>
</html>
结果发现点击后立马滑动滚轮,页面无反应,约2s后才有滚动效果,此时控制台也会打印出警告:
Handling of 'wheel' input event was delayed for 1279 ms due to main thread being busy. Consider marking event handler as 'passive' to make the page more responsive.
由于主线程繁忙,'wheel'输入事件的处理延迟了1279毫秒。 考虑将事件处理程序标记为“passive”,以使页面响应更快。
后面的就不细说了,将passive设置为true后,再执行上述操作,页面滚动就很流畅了
为什么设置为passive就不卡了呢?
在没有passive的时候,浏览器会等待回调函数的执行,然后再去执行滚动操作
设置passive后,滑动滚轮时浏览器会直接对内容快照(相当于给当前页面拍了一张照片)进行滑动处理,因此滑动会很流畅
解决passive警告
先别急着装!!
本项目是vue项目,我使用了default-passive-events这个包来解决passive警告,这个包的原理是给每一个事件监听器都添加一个passive:true
npm i default-passive-events -S安装- main.js中添加
import 'default-passive-events'
前面说过,本项目中的这个警告是element-ui和echarts两个库导致的,虽然在大部分场景下可以解决这个报警,但是当这两个库中确实有些事件调用了preventDefault时,你稍微滑动下就会疯狂报错,因此一定要慎用
使用default-passive-events导致的报错
如果项目中确实有方法需要调用preventDefault,而我们又引入了default-passive-events这个包,这个时候控制台就会疯狂报错
Unable to preventDefault inside passive event listener invocation.
因为我们明明告诉浏览器不会阻止默认行为了,可我们的代码还是阻止了。
目前的解决办法似乎只有不用这个包,继续忍受着passive警告,毕竟警告总比报错好一点
参考
MDN关于passive的介绍
让页面滑动流畅得飞起的新特性:Passive Event Listeners


















