停止特定处理程序的事件传播

时间:2012-04-11 22:38:18

标签: javascript html events javascript-events

tl; dr Summary

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

简单示例

鉴于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只能在<button>上处理一次,这是在特定传播链中注册的最多叶元素。

“工作”解决方案

IE9,Chrome18,FF11,Safari5和Opera11上的以下JavaScript代码has been tested工作

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. 你能想到一种更优雅的方法来检测当前发送事件时是否已经看到相同的事件处理函数吗?
  5. 真实世界应用

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

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

    ...和a user who wants能够通过拖动整个组来拖动整个组,并且还可以通过拖动它来拖动圆圈。

    现在混合使用generic dragging library来处理大部分细节。我建议修改此库,以便如果用户向<g><circle>添加拖动处理程序,则拖动圆圈会自动阻止拖动从群组启动,但不会停止传播所有mousedown事件(因为用户可能有一个高级处理程序用于其他目的)。

2 个答案:

答案 0 :(得分:0)

这是一个通用的解决方案,适用于所有主流浏览器的当前版本......但不适用于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;
}

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

答案 1 :(得分:-1)

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属性,这意味着同样的事情。