addEventListener中的passive属性踩坑

Jun 23, 2018 22:42 · 216 words · 2 minute read 事件

起因

在移动端使用event.preventDefault()来阻止touchstarttouchmove事件发现无法实现

打开Chrome控制台发现打印了如下文字

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

点进网页看看发生了什么!

AddEventListenerOptions defaults passive to false. With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored)..

If the value is explicitly provided in the AddEventListenerOptions it will continue having the value specified by the page.

This is behind a flag starting in Chrome 54, and enabled by default in Chrome 56. See https://developers.google.com/web/updates/2017/01/scrolling-intervention

大意是:为了移动端性能考虑,在监听touchstarttouchmove时,默认设置addEventListener中的passivetrue(这样就导致了preventDefault失效)

为什么Chrome这样做?

https://developers.google.com/web/updates/2017/01/scrolling-intervention

Performance! 可以减少250ms左右的延迟

Passive event listeners是2016年Google I/O 上同 PWA 概念一起被提出,但是同PWA不同,Passive event listeners 的作用很简单,如果用简单一句话来解释就是:提升页面滑动的流畅度。

Chrome 51 和 Firefox 49 已经支持 passive 属性。如果浏览器不支持,已经有人做了非常巧妙地 polyfill

// Test via a getter in the options object to see 
// if the passive property is accessed
var supportsPassive = false;
try {
  var opts = Object.defineProperty({}, 'passive', {
    get: function() {
      supportsPassive = true;
    }
  });
  window.addEventListener("test", null, opts);
} catch (e) {}

// Use our detect's results. 
// passive applied if supported, capture will be false either way.
elem.addEventListener(
  'touchstart',
  fn,
  supportsPassive ? { passive: true } : false
); 

在Vue中你可以通过如下代码来提升移动端性能够

<div v-on:scroll.passive="onScroll">...</div>

在React和其他框架/非框架中只能手动绑定了

elem.addEventListener('touchmove', func, { passive: true })

记得用完解绑!

elem.removeEventListener('touchmove', func, false)

解决方法

设置touch-action?

在网上搜了很多方法,有不少答案都指向设置touch-action: none

虽然设置后的确Chrome的确不报提示了,但是preventDefault依然没有生效

addEventListener中设置passive

只能自己手动来绑定事件了

elem.addEventListener('touchmove', func, { passive: false })

同样,用完要记得解绑~

elem.removeEventListener('touchmove', func, false)

参考文章

https://segmentfault.com/a/1190000007913386