你不知道的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。对于第三个参数,如果 passiveSupportedtrue ,我们传递了一个 passive 值为 trueoptions 对象;如果相反的话,我们知道要传递一个布尔值,于是就传递 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
); 

猜你喜欢

转载自blog.csdn.net/fesfsefgs/article/details/107852438