前端开发浏览器Added non-passive event listener to a scroll-blocking警告处理

1,194 阅读2分钟

浏览器控制台输出:[Violation] Added non-passive event listener to a scroll-blocking <某些> 事件. Consider marking event handler as 'passive' to make the page more responsive. See <URL>

image.png

弃案:刚开始使用的是插件default-passive-events,但是发现使用了之后某些UI库的组件会有问题。例如 ng-zorro-antd/date-picker选择时间后,会报错 然后输入框不会出现选择的日期。故放弃。

第一步,找出是哪个组件/类发出的警告

在浏览器控制台输入:

(function() {
  const originalAddEventListener = EventTarget.prototype.addEventListener;
  EventTarget.prototype.addEventListener = function(type, listener, options) {
    if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
      // 获取更详细的元素信息
      const elementInfo = {
      target:this,
        // 元素自己的信息
        self: {
          tagName: this.tagName,
          id: this.id,
          className: this.className,
          classList: Array.from(this.classList || []), // 打印所有类名
          attributes: Array.from(this.attributes || []).map(attr => ({
            name: attr.name,
            value: attr.value
          }))
        },
        // 父元素信息
        parentElement: this.parentElement ? {
          tagName: this.parentElement.tagName,
          id: this.parentElement.id,
          className: this.parentElement.className,
          classList: Array.from(this.parentElement.classList || [])
        } : null,
        // 所有父元素的类名
        parentClasses: (function(element) {
          const classes = [];
          let current = element;
          while (current && current.parentElement) {
            current = current.parentElement;
            if (current.className) {
              classes.push({
                tagName: current.tagName,
                id: current.id,
                className: current.className,
                classList: Array.from(current.classList || [])
              });
            }
          }
          return classes;
        })(this)
      };

      console.warn('Event Listener Added:', {
        target:this,
        eventType: type,
        elementInfo,
        options,
        stack: new Error().stack,
        timestamp: new Date().toISOString()
      });
    }
    return originalAddEventListener.apply(this, arguments);
  };
  console.log('Event listener monitor installed');
})();

然后回打印发出警告的元素详情信息

image.png

第二步 隐藏这个提示。给发出提示的组件相关事件添加 { passive: true }

然后根据需要在入口文件或者指定组件中执行下面代码:

  handleScrollBlockingWarning(){
    const originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function (type, listener, options) {
      // 需要处理的事件类型
      const passiveEvents = ['touchstart', 'touchmove', 'wheel', 'mousewheel'];
      if (passiveEvents.includes(type)) {
        const isAntTabsNavList = this instanceof HTMLElement &&
          this.classList.contains('ant-tabs-nav-list');
        if (isAntTabsNavList) {
          // 对于 ant-tabs-nav-list,强制使用 passive: true
          options = typeof options === 'boolean'
            ? { passive: true, capture: options }
            : { ...(options || {}), passive: true };
        }
      }
      return originalAddEventListener.call(this, type, listener, options);
    };
  }

设置后,当组件触发这些事件时['touchstart', 'touchmove', 'wheel', 'mousewheel'],控制台会报错

Unable to preventDefault inside passive event listener

报错原因是在设置了passive: true 的监听事件中。调用了  e.preventDefault();

例如:

element.addEventListener('touchmove', function(e) {

  e.preventDefault();

}, { passive: true });

第三步,将报错的事件中  e.preventDefault 置为空方法

然后在拦截事件时把它的e.preventDefault置为空方法。
e.preventDefault = function () {};


  handleScrollBlockingWarning() {
    const originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function (type, listener, options) {


      // 需要处理的事件类型
      const passiveEvents = ['touchstart', 'touchmove', 'wheel', 'mousewheel'];
      if (passiveEvents.includes(type)) {
        const isBarChartDiv = this instanceof HTMLElement && this.tagName === 'DIV' &&
          this.parentElement &&
          this.parentElement.classList.contains('echart-div');
        const isAntTabsNavList = this instanceof HTMLElement &&
          this.classList.contains('ant-tabs-nav-list');
        if (isAntTabsNavList || isBarChartDiv) {
          const originalListener = listener;
          listener = function (this: EventTarget, e) {
            // 替换 preventDefault
            const originalPreventDefault = e.preventDefault;
            e.preventDefault = function () {
              // 什么都不做
              // 你也可以加日志
              // console.warn('preventDefault 被拦截');
            };
            try {
              if (typeof originalListener === 'function') {
                // 是函数,直接调用
                originalListener.call(this, e);
              } else if (originalListener && typeof originalListener.handleEvent === 'function') {
                // 是对象,调用 handleEvent
                originalListener.handleEvent.call(originalListener, e);
              }
            } finally {
              // 恢复原方法,避免影响其他事件
              e.preventDefault = originalPreventDefault;
            }
          };
        }


        if (isAntTabsNavList) {
          // console.warn("handleScrollBlockingWarning---", listener);
          // 对于 ant-tabs-nav-list,强制使用 passive: true
          options = typeof options === 'boolean'
            ? { passive: true, capture: options }
            : { ...(options || {}), passive: true };
        } else if (isBarChartDiv) {
          // console.warn("handleScrollBlockingWarning---", listener);
          options = typeof options === 'boolean'
            ? { passive: true, capture: options }
            : { ...(options || {}), passive: true };
        }
      }
      return originalAddEventListener.call(this, type, listener, options);
    };
  }

这样就可以解决

但是这样处理的组件,如果需要用到wheel手势。可能会出现手势冲突。 例如:这个组件的父组件也要响应wheel事件,那么上面的代码就会导致。两个组件同时响应wheel事件。

感觉有点得不偿失。因为要面临组件一些未知的手势冲突,可根据实际情况决定是否使用该方法