scroll 行为 vs scroll 事件

文档视图或者一个元素在滚动时,会触发元素的 scroll 事件,先有滚动行为,然后才会触发 scroll 事件。scroll 事件的 event.cancelable 属性默认是 false,是一种不可被取消的事件,调用 event.preventDefault()没有任何意义,并不能阻止浏览器滚动

passive 事件原因

由于 touchstart/touchmove/wheel 事件对象的 cancelable 属性为 true,也就是说它的默认行为可以被监听器通过 preventDefault() 方法阻止,那它的默认行为是什么呢,通常来说就是滚动当前页面(还可能是缩放页面),如果它的默认行为被阻止了,页面就必须静止不动。但浏览器无法预先知道一个监听器会不会调用 preventDefault() ,它能做的只有等监听器执行完后再去执行默认行为,而监听器执行是要耗时的,有些甚至耗时很明显,这样就会导致页面卡顿。视频里也说了,即便监听器是个空函数,也会产生一定的卡顿,毕竟空函数的执行也会耗时。

有 80% 的滚动事件监听器是不会阻止默认行为的,也就是说大部分情况下,浏览器是白等了。所以,passive 监听器诞生了,它表示当前通过 addEventListener 注册的 handler 是一个被动监听器,相当于标记当前 event.cancelable=false ,告诉浏览器 滚动行为 不会被阻止,你可以立即执行滚动行为, 浏览器知道了一个监听器是 passive 的,它就可以在两个线程里同时执行监听器中的 JavaScript 代码和浏览器的默认行为了。

当标记为 passive=true 之后,即使调用 event.preventDefault() 也不会阻止浏览器滚动,只会再控制台输出一条警告信息。

passive detect

通过在 options 对象中定义一个 getter 方法来检测 passive 属性是否被访问到

var supportsPassive = false;
try {
    var opts = Object.defineProperty({}, "passive", {
        get: function () {
            supportsPassive = true;
        },
    });
    window.addEventListener("test", null, opts);
} catch (e) {
}

// 使用检测结果。如果支持则使用 passive,否则将捕获设置为 false
elem.addEventListener(
    "touchstart",
    fn,
    supportsPassive ? {passive: true} : false
);

reference

Last Updated:
Contributors: lizonglin