简体   繁体   English

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

[英]Stop Event Propagation for a Specific Handler

tl;dr Summary tl;博士总结

Write a function that—when registered to handle a specific event on multiple elements in a hierarchy—executes on the first element reached during bubbling but does not execute (or returns early) when bubbling further up the hierarchy.编写一个函数,当注册以处理层次结构中多个元素的特定事件时,在冒泡期间到达的第一个元素上执行,但在进一步向上冒泡时不执行(或提前返回)。 (Without actually stopping propagation of the event.) (实际上没有停止事件的传播。)

Simple Example简单示例

Given this HTML鉴于此 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>

…clicking on the button yields this output: …单击按钮会产生以下输出:

drag BUTTON拖动按钮
click DIV点击 DIV
drag DIV拖动 DIV

However, I don't want to drag the div.但是,我不想拖动 div。 Specifically, I want startDrag to only be processed once, on the <button> , the leaf-most element for which it is registered in a particular propagation chain.具体来说,我希望startDrag只在<button>上被处理一次,这是它在特定传播链中注册的最叶元素。

"Working" solution “工作”解决方案

The following JavaScript code has been tested to work on IE9, Chrome18, Firefox11, Safari5, and Opera11.以下 JavaScript 代码已经过测试,可IE9、Chrome18、Firefox11、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;
}

The above does not work with IE8, even if you use the global window.event object;以上不适用于 IE8,即使您使用全局window.event对象; the same event object is not reused during bubbling.在冒泡期间不会重用相同的event对象。

The Question(s)问题

However, I'm not sure if the above is guaranteed to work…nor if it's as elegant as it could be.但是,我不确定上述是否可以保证工作......也不确定它是否尽可能优雅。

  1. Is the same event object guaranteed to be passed up the chain during propagation?相同的event对象是否保证在传播过程中向上传递?
  2. Is the event object guaranteed to never be re-used for different event dispatches? event对象是否保证永远不会被重新用于不同的事件调度?
  3. Is the event object guaranteed to support having an expando property added to it? event对象是否保证支持向其添加 expando 属性?
  4. Can you think of a more elegant way to detect if the same event handler function has been seen already on the current dispatch of an event?你能想出一种更优雅的方法来检测在当前事件调度中是否已经看到相同的事件处理函数吗?

Real World Application现实世界的应用

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

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

…and a user who wants to be able to drag the whole group by dragging on any rect within it, and also to be able to drag just the circle by dragging on it. ...以及希望能够通过拖动其中的任何矩形来拖动整个组的用户,并且还希望能够通过拖动来仅拖动圆圈。

Now mix in a generic dragging library that handles most of the details for you.现在混入一个为您处理大部分细节的通用拖动库 I'm proposing to modify this library such that if the user adds a drag handler to both the <g> and the <circle> then dragging on the circle automatically prevents the dragging from initiating on the group, but without stopping propagation of all mousedown events (since the user might have a high level handler for other purposes that is desirable).我提议修改这个库,这样如果用户向<g><circle>添加了一个拖动处理程序,那么在圆圈上拖动会自动阻止拖动在组上启动,但不会停止所有mousedown的传播事件(因为用户可能有一个用于其他目的的高级处理程序)。

Here's a generic solution that works in the current versions of all major browsers...but not in IE8 and may not be guaranteed to work in future versions:这是适用于所有主要浏览器当前版本的通用解决方案......但不适用于 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
}

Alternatively, here is a less-generic solution that is slightly-but-probably-not-noticeably faster (again, not in IE8 and maybe not guaranteed to work in the future):或者,这是一个不太通用的解决方案,它稍微快一点但可能不会明显更快(同样,不在 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;
}

I will happily switch the acceptance to another answer with proper specs provided.我很乐意将接受转换为提供适当规格的另一个答案。

This looks good in Firefox, and can probably be adapted to non-standard browsers, since jQuery essentially does this with delegate and live .在 Firefox 中看起来不错,并且可能适用于非标准浏览器,因为 jQuery 本质上是通过delegatelive做到这一点的。 It uses stopPropagation and target , both of which are standard.它使用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();
}

For me, the order when you click on the button is reversed.对我来说,单击按钮时的顺序是相反的。 Yours has "drag BUTTON" then "click DIV", while mine is the opposite.你的有“拖动按钮”然后“单击 DIV”,而我的则相反。 However, I think it's undefined in the original.但是,我认为它在原始文件中是未定义的。

EDIT: Changed to use target .编辑:更改为使用target IE before 9 uses a non-standard srcElement property that means the same thing. 9 之前的 IE 使用非标准的srcElement属性,这意味着同样的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM