1

tl;博士总结

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

简单示例

鉴于此 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>

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

拖动 BUTTON
点击 DIV
拖动 DIV

但是,我不想拖动 div。具体来说,我只想在它在特定传播链中注册的最叶元素startDrag上处理一次。<button>

“工作”解决方案

以下 JavaScript 代码已经过测试,可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;
}

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

问题

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

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

现实世界的应用

想象一下这个 SVG 层次结构……</p>

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

...以及希望能够通过拖动其中的任何矩形来拖动整个组的用户,并且还希望能够通过拖动来仅拖动圆圈。

现在混入一个为您处理大部分细节的通用拖动库。我提议修改这个库,这样如果用户同时添加一个拖动处理程序,<g>然后<circle>在圆圈上拖动会自动阻止拖动在组上启动,但不会停止所有mousedown事件的传播(因为用户可能有用于其他需要的高级处理程序)。

4

2 回答 2

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;
}

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

于 2012-04-12T14:28:37.003 回答
-1

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

于 2012-04-11T23:07:10.730 回答