将库添加的触摸和滚轮事件侦听器设为 "passive"

Make touch and wheel event listeners added by libraries as "passive"

我有两个项目:

  1. BootstrapjQuery
  2. materialize-cssVanilla JS

当 运行 对两个项目进行 Lighthouse 审核时,我曾经收到此警告,原因是一个项目 materialize-css 和另一个项目 jQuery:

我说“曾经得到”,因为我确实设法为 jQuery 修复了它,只是应用了这个解决方法:

const opts = (ns) => ... // some code deciding if browser supports passive
$.event.special.touchmove = { setup: function(_, ns, handle) { this.addEventListener('touchmove', handle, opts(ns)) } }
$.event.special.touchstart = { setup: function(_, ns, handle) { this.addEventListener('touchstart', handle, opts(ns)) } }
$.event.special.touchend = { setup: function(_, ns, handle) { this.addEventListener('touchend', handle, opts(ns)) } }

这似乎解决了 jQuery 的问题,我不再收到此类警告,一切似乎都正常。

现在,对于 materialize-css,我发现这个包 default-passive-events(来自文档):

It basically will set { passive: true } automatically every time you declare a new event listener.

不幸的是,由于使用 e.preventDefault()...

,此库确实使实体化组件因触摸事件而中断

是否有类似于上述 jQuery 解决方法的方法来修复所有 materialize-css 添加的事件侦听器? P.S。它不使用 jQuery

首先,这只是一个警告,不是错误。


Is there a way, similar to jQuery workaround above, to fix all the materialize-css added event listeners? P.S. It does not use jQuery

是的,实际上有三种方式:

  1. 在不提高性能的情况下阅读警告

只需将 { passive: false } 作为第三个参数添加到所有没有对象作为第三个参数的侦听器。这将告诉浏览器可能会在这些事件上调用 .preventDefault()。但是,尤其是在 scrolltouchmovetouchstart 事件上,当浏览器知道默认行为不会在事件上被阻止时,性能提升是相当可观的。当标记为被动时,滚动会更平滑,感知性能会显着提高。

  1. 通过可能破坏功能来提高性能

{ passive: true } 作为第三个参数添加到所有没有对象作为第三个参数的侦听器。这将告诉浏览器永远不会在这些事件上调用 .preventDefault()。您会看到性能提升,但依赖于防止这些事件的代码将会中断。

注意:这就是 jQuery 修复和 default-passive-events 包所做的,顺便说一句。

  1. 正确的方法

正确的方法是进入你正在修复的任何库的源代码,弄清楚哪些事件可能会被阻止并为那些添加 { passive: false },同时为所有内容添加 { passive: true }否则。
我认为在库中找到所有阻止事件发生的地方并不是一项艰巨的任务。
您可以在 fork 中执行此操作,最好将其 PR 返回到 lib 的存储库中,以便其他人受益,就像您从 lib 本身中受益一样。


这是解决方案 1。

function patchScrollBlockingListeners() {
  let supportsPassive = false;
  const x = document.createElement("x");
  x.addEventListener("cut", () => 1, {
    get passive() { supportsPassive = true; return !!1 }
  });
  x.remove();
  if (supportsPassive) {
    const originalFn = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(...args) {
      if (
        ['scroll', 'touchmove', 'touchstart'].includes(args[0]) &&
        (typeof args[2] !== 'object' || args[2].passive === undefined)
      ) {
        args[2] = {
          ...(typeof args[2] === 'object' ? args[2] : {}),
          passive: false
        };
      }
      originalFn.call(this, ...args);
    }
  }
}
patchScrollBlockingListeners();

以上代码仅“修补”scrolltouchmovetouchstart 事件(通过声明它们是非被动的)。这会使警告消失,而无需触及第三方代码。
注意:为了使其工作,函数必须是 运行 在加载任何库抛出警告之前。上面的代码只修补了 运行 之后添加的事件,它没有修补已经绑定的侦听器。

注意:解决方案 2 是相同的代码,只是 passive 覆盖设置为 true

另一个相当重要的注意事项: 虽然我不能保证它对每个人都有效,但将以下内容传递给 passive 对我有用“修补”很多项目中的很多库:

passive: typeof args[2] === "boolean" ? args[2] : true

它遵循以前的 addEventListener 语法(其中第 3 个参数是 passive 本身,如 boolean)并在根本未指定时将其设置为 true .然而,这将中断未指定 passive 的事件,并且在某些情况下事件被取消,这就是为什么我没有在上面包含它。

我创建了一个遵循@tao 的“正确方法” 的 npm 包,不仅消除了警告,而且实际上提高了性能。参见 passive-events-support

它确实为我删除了由 MaterializeCSSjQuery 引起的警告。如果您想使用它,请务必阅读自定义部分以仅对您需要的事件应用修复:

import { passiveSupport } from 'passive-events-support/src/utils'

passiveSupport(['touchstart', 'touchmove'])

如果您不使用模块,请阅读文档了解将其导入项目的其他方法。