给阻止滚动的“touchstart”事件添加了非被动事件监听器

51

今天突然间,我在我们网站的每个页面上都看到了这个东西。

Added non-passive event listener to a scroll-blocking 'touchstart' event.
Consider marking event handler as 'passive' to make the page more responsive
并不只是一两次... 而是成千上万次... 输入图像描述 它们正在疯狂运行。 唯一停止违规洪流的方法是将此行注释掉。
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>

我看了其他帖子关于这个违规是什么意思的内容,但我真的看不出我两个小时前和现在有什么不同(我做了一个完全回滚来看看是否有所帮助)

就像有人把bug放进了jquery.min.js里一样,但我非常怀疑,因为那样每个人都会遇到这个问题。

有什么想法吗?我尝试调试了所有可以调试的东西,但仍然不知道是什么原因造成的?!?

更新

我用 <button data-toggle="tooltip" title="text"></button> 替换了所有的 <button><md-tooltip>text</md-tooltip></button>,这就消除了99%的违规。


它在您的旧版本中是否仍然存在?因为这可能是浏览器更新添加的... - Salketer
嗯,好问题...今天我所做的唯一主动行为就是添加了 Speed Dial 2 - New tab 2.2.1 作为 Chrome 浏览器扩展程序(可以控制空白页快捷方式)- 但是我卸载了它,以防它引起问题。 - torbenrudgaard
尝试在Firefox上运行页面,这里的jQuery在压缩代码的深处进入了无限循环。 - torbenrudgaard
1
好的。尝试用未压缩的版本替换你的jQuery文件。在查看错误时,你会有更好的想法。 - Salketer
7个回答

42

这对我解决了问题:

jQuery.event.special.touchstart = {
  setup: function( _, ns, handle ){
    if ( ns.includes("noPreventDefault") ) {
      this.addEventListener("touchstart", handle, { passive: false });
    } else {
      this.addEventListener("touchstart", handle, { passive: true });
    }
  }
};

7
我应该把这个放在哪里?在JS文件中还是在引入JS文件后面? - WilliamK
3
这段话的翻译如下:这对我很有效,我只需要复制这段代码就能够消除关于“touchmove”的警告(将每个“touchstart”替换为“touchmove”)。从这个jsbin(http://jsbin.com/bupesajoza/edit?html,js,output)中我了解到,“noPreventDefault”部分的目的是为了可能不使用'passive'监听器。 - Giorgio Tempesta
2
if/else块的内容应该交换:如果命名空间包含noPreventDefault,则被动态的默认值应该是true - Kshatra
2
jQuery.event.special.touchstart = { setup: function( _, ns, handle ){ if ((ns.indexOf('noPreventDefault') > -1)) { this.addEventListener("touchstart", handle, { passive: false }); } else { this.addEventListener("touchstart", handle, { passive: true }); } } };兼容IE11版本: - Dmitri Tsoy
1
@FrozenCrayon ns 可能代表命名空间 (namespace)。 - magic_turtle
显示剩余7条评论

21

我正在使用各种事件,这似乎解决了我的使用情况

(function () {
    if (typeof EventTarget !== "undefined") {
        let func = EventTarget.prototype.addEventListener;
        EventTarget.prototype.addEventListener = function (type, fn, capture) {
            this.func = func;
            if(typeof capture !== "boolean"){
                capture = capture || {};
                capture.passive = false;
            }
            this.func(type, fn, capture);
        };
    };
}());

1
好的,这对我有用。我将它添加到我的jQuery文件底部,命名为jquery.custom.min.js并进行了压缩。非常好用。 - Floris
在Internet Explorer中,EventTarget似乎未定义。因此,我建议在var func =行之前添加if (typeof EventTarget!=='undefined') { - Floris
我更喜欢这个解决方案,但是在控制台中出现了以下错误:[Violation] Added synchronous DOM mutation listener to a 'DOMNodeRemoved' event. Consider using MutationObserver to make the page more responsive. 我还没有想出如何使用Mutation Observers重写它。请帮忙吗? - Cogicero
我在为谷歌地图标记添加动画时遇到了主题发起者的问题。这个解决方案有效。 - djdance

14

深挖一些,这并不是一个新的行为,它已经有一段时间被报告过了,但 jQuery 仍未修复它。

问题在于,对于一个处理程序要想成为 passive ,它必须确定永远不会调用 preventDefault() ,但是 jQuery 事先并不知道......

我唯一能给你的提示是更改你的控制台日志级别并删除“详细”内容。关注此问题以获取解决方案的想法。


好的Salketer - 我发现了一些事情... <md-tooltip>testing</md-tooltip> 是导致这个违规的一个原因,而且由于我使用了很多这样的东西,所以我得到了很多错误。我仍在检查,因为我确定还有其他原因导致它。 - torbenrudgaard
我只凭那些片段无法为您提供更多帮助。但基本上,所有引起滚动的事件(滚轮、触摸、滚动)都会抛出此事件,因为 jQuery 事件处理可能会阻止它们。因此,请尝试在输入框上删除该滚轮事件,这非常奇怪... 然后查看其他事件。您还可以通过使用事件委托来减少处理程序的数量。 - Salketer
现在我已经替换了所有的<button><md-tooltip>text</md-tooltip></button>宽度<button data-toggle="tooltip" title="text"></button>这样就消除了99%的违规。 - torbenrudgaard
我认为这不应该成为被接受的答案,因为大部分原因在于最后一行:“唯一的提示是...删除'Verbose'。”我的意思是,那只是一个糟糕的建议。 - mrk
@Salketer 我的观点是,我认为保持Verbose开启可以为开发人员提供非常重要的信息。它可以突出显示代码中的具体位置,例如setTimeout处理程序运行时间过长(可能会影响网站的可用性),或者强制回流发生时,这可能会导致太多的重新渲染。Verbose设置提供了很好的信息,我认为应该在DevTools中默认开启,因为谁最常打开DevTools呢?我认为开发人员比其他任何用户更经常打开DevTools。 - mrk
显示剩余4条评论

8
从Sergio的回答来看,将jquery脚本添加在底部。如果存在touchstart和touchmove问题,请添加相同的代码并用touchmove替换touchstart,像这样:
jQuery.event.special.touchstart = {
  setup: function( _, ns, handle ){
    if ( ns.includes("noPreventDefault") ) {
      this.addEventListener("touchstart", handle, { passive: false });
    } else {
      this.addEventListener("touchstart", handle, { passive: true });
    }
  }
};
jQuery.event.special.touchmove = {
  setup: function( _, ns, handle ){
    if ( ns.includes("noPreventDefault") ) {
      this.addEventListener("touchmove", handle, { passive: false });
    } else {
      this.addEventListener("touchmove", handle, { passive: true });
    }
  }
};

7
@neel-singh的答案有所改进
(function () {
  if (typeof EventTarget !== 'undefined') {
    let supportsPassive = false;
    try {
      // Test via a getter in the options object to see if the passive property is accessed
      const opts = Object.defineProperty({}, 'passive', {
        get: () => {
          supportsPassive = true;
        },
      });
      window.addEventListener('testPassive', null, opts);
      window.removeEventListener('testPassive', null, opts);
    } catch (e) {}
    const func = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function (type, fn) {
      this.func = func;
      this.func(type, fn, supportsPassive ? { passive: false } : false);
    };
  }
})();

1

我认为除了基于触摸的事件,您还可以添加基于滚动的修复措施,以防止谷歌页面评分将其标记为桌面版与移动版不同:

jQuery.event.special.wheel = {
    setup: function( _, ns, handle ){
        this.addEventListener("wheel", handle, { passive: true });
    }
};
jQuery.event.special.mousewheel = {
    setup: function( _, ns, handle ){
        this.addEventListener("mousewheel", handle, { passive: true });
    }
};

0

只需编写一个辅助函数,并为需要调用的所有事件调用它。

const preventPassiveWarning = event => {
    jQuery.event.special[event] = {
        setup: function (_, ns, handle) {
            if (ns.includes("noPreventDefault")) {
                this.addEventListener(event, handle, { passive: false });
            } else {
                this.addEventListener(event, handle, { passive: true });
            }
        }
    }
}
//Call it here 
preventPassiveWarning('touchstart')
preventPassiveWarning('touchmove')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接