【问题标题】:Stop Event Propagation for a Specific Handler停止特定处理程序的事件传播
【发布时间】:2012-04-11 22:38:18
【问题描述】:

tl;博士总结

编写一个函数,当注册以处理层次结构中多个元素上的特定事件时,该函数在冒泡期间到达的第一个元素上执行,但在进一步向上冒泡时不执行(或提前返回)。 (实际上并未停止事件的传播。)

简单示例

给定this HTML

<!DOCTYPE html>
<body>
<div><button>click</button></div>
<script>
var d = document.querySelector('div'),
    b = document.querySelector('button');
d.addEventListener('mousedown',clickPick,false);
d.addEventListener('mousedown',startDrag,false);
b.addEventListener('mousedown',startDrag,false);
function clickPick(){ console.log('click',this.tagName); }
function startDrag(){ console.log('drag',this.tagName);  }
</script>

...单击按钮会产生以下输出:

拖动按钮
点击 DIV
拖动DIV

但是,我不想拖动 div。具体来说,我希望startDrag 只在&lt;button&gt; 上被处理一次,这是它在特定传播链中注册的最叶子元素。

“工作”解决方案

以下 JavaScript 代码 has been tested 可在 IE9、Chrome18、FF11、Safari5 和 Opera11 上工作

function startDrag(evt){
  if (seenHandler(evt,startDrag)) return;
  console.log('drag',this.tagName);
}

function seenHandler(evt,f){
  if (!evt.handlers) evt.handlers=[];
  for (var i=evt.handlers.length;i--;) if (evt.handlers[i]==f) return true;
  evt.handlers.push(f);
  return false;
}

即使您使用全局 window.event 对象,上述内容也适用于 IE8;冒泡期间不会重复使用相同的event 对象。

问题

但是,我不确定上述方法是否保证可以工作......也不确定它是否尽可能优雅。

  1. 相同的event 对象是否保证在传播过程中向上传递?
  2. event 对象是否保证不会被重复用于不同的事件调度?
  3. event 对象是否保证支持向其添加 expando 属性?
  4. 您能想出一种更优雅的方法来检测当前调度事件时是否已经看到相同的事件处理函数?

现实世界的应用

想象一下这个 SVG 层次结构……

<g>
  <rect … />
  <rect … />
  <circle … />
</g>

...和a user who wants 能够通过拖动其中的任何矩形来拖动整个组,并且还能够通过拖动来仅拖动圆圈。

现在混入一个generic dragging library,它会为您处理大部分细节。我提议修改这个库,这样如果用户向&lt;g&gt;&lt;circle&gt; 添加一个拖动处理程序,那么在圆圈上拖动会自动阻止拖动在组上启动,但不会停止所有@ 的传播987654337@ 事件(因为用户可能有一个高级处理程序用于其他需要的目的)。

【问题讨论】:

    标签: javascript html events javascript-events


    【解决方案1】:

    这是一个通用解决方案,适用于所有主要浏览器的当前版本...但不适用于 IE8,并且可能无法保证在未来版本中运行:

    // Solution supporting arbitrary number of handlers
    function seenFunction(evt,f){
      var s=evt.__seenFuncs;
      if (!s) s=evt.__seenFuncs=[];
      for (var i=s.length;i--;) if (s[i]===f) return true;
      evt.handlers.push(f);
      return false;
    }
    
    // Used like so:
    function myHandler(evt){
      if (seenHandler(evt,myHandler)) return;
      // ...otherwise, run code normally
    }
    

    或者,这里有一个不太通用的解决方案,它稍微快一点但可能不会明显更快(同样,不在 IE8 中,并且可能不保证在未来工作):

    // Implemented per handler; small chance of error with a conflicting name
    function myHandler(evt){
      if (evt.__ranMyHandler) return;
      // ...otherwise, run code normally
      evt.__ranMyHandler = true;
    }
    

    我很乐意将接受转换为提供适当规格的另一个答案。

    【讨论】:

      【解决方案2】:

      This 在 Firefox 中看起来不错,并且可能适用于非标准浏览器,因为 jQuery 本质上是通过 delegatelive 来实现的。它使用 stopPropagationtarget ,这两个都是标准的。

      var d = document.querySelector('div'),
          b = document.querySelector('button');
      d.addEventListener('mousedown',clickPick,false);
      d.addEventListener('mousedown',startDrag,false);
      function clickPick(evt){
          console.log('click', this.tagName);
      }
      function startDrag(evt){
          console.log('drag', evt.target.tagName);
          evt.stopPropagation();
      }
      

      对我来说,单击按钮时的顺序是相反的。你的有“拖动按钮”然后“单击 DIV”,而我的则相反。但是,我认为它在原版中是未定义的。

      编辑:改为使用target。 9 之前的 IE 使用非标准的 srcElement 属性,这意味着同样的事情。

      【讨论】:

      • 感谢您的努力,但我似乎把用例简化得太多了。给定层次结构中不同元素和不同处理程序上相同事件的一组注册,无法控制注册顺序或元素选择,我需要一种方法让特定处理程序只为第一个(最低)运行自身它在链中命中的元素。
      • @Phrogz,如果您的真实情况是一般情况,我可能无法帮助您。但是,如果您使您的示例更加现实(而不是试图涵盖所有理论上的可能性),我会尝试相应地调整我的答案。
      • 我已经更新了问题,以便更清楚地了解现实世界的需求。
      猜你喜欢
      • 1970-01-01
      • 2014-05-06
      • 1970-01-01
      • 2019-07-08
      • 1970-01-01
      • 2014-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多