如何克隆或重新调度DOM事件?


70

我正在寻找仅克隆或重新调度DOM事件的简单抽象方法。我对克隆DOM节点不感兴趣。

我做了一些实验,阅读了DOM Events规范,但没有找到明确的答案。

理想情况下,我正在寻找简单明了的东西:

handler = function(e){
  document.getElementById("decoy").dispatchEvent(e)
}
document.getElementById("source").addEventListener("click", handler)

当然,此代码示例不起作用。有一个DOM异常说明该事件当前正在分派-很明显。

我想避免使用手动创建新事件document.createEvent(),对其进行初始化和调度。

这个用例有一个简单的解决方案吗?


为什么需要重新分配事件?
josh3736

我需要此方法作为当前不调度子节点事件的CSS区域的解决方法。区域仅呈现子节点的内容,而不充当父节点。
拉兹万·卡利曼

Answers:


131

我知道,这个问题很老,OP希望避免创建/初始化方法,但是有一种相对简单的方法来复制事件:

new_event = new MouseEvent(old_event.type, old_event)

如果您不仅需要鼠标事件,还可以执行以下操作:

new_event = new old_event.constructor(old_event.type, old_event)

并在原始上下文中:

handler = function(e) {
  new_e = new e.constructor(e.type, e);
  document.getElementById("decoy").dispatchEvent(new_e);
}
document.getElementById("source").addEventListener("click", handler);

(对于jQuery用户:您可能需要使用e.originalEvent.constructor代替e.constructor


3
请注意,这仅适用于现代版本的Firefox和Chrome,不适用于任何版本的IE
Quinn Strahl 2014年

3
@Alexis我正在:Uncaught TypeError: Illegal constructor:(
niieani 2014年

2
我刚刚在npm上放了一个称为clone-event的模块,该模块本质上包装了此答案中描述的功能,这是我现在要重新调度事件时使用的功能。我已经在Chrome v37中对该方法进行了基本测试,而且似乎可行。如果有人发现错误或无法正常工作的环境,我将不胜感激。
KP MacGregor

2
非常聪明,您将旧事件作为构造函数的init参数传递的方式!
体面的达布尔2015年

2
@Gyro,是的,根据规范,在大多数情况下,不受信任的事件无法“对它们执行默认操作,就像已调用了preventDefault()方法一样”。不幸的是,我认为我们对此无能为力。
亚历克西斯(Alexis)2016年

2

Internet Explorer修复程序

Alexis发布了一个不错的解决方案,但他的解决方案无法在Internet Explorer中使用。下面的解决方案将。不幸的是,没有像Internet Explorer中的事件构造器那样一致的系统,因此下面的代码膨胀是必要的。

var allModifiers = ["Alt","AltGraph","CapsLock","Control",
                    "Meta","NumLock","Scroll","Shift","Win"];
function redispatchEvent(original, newTargetId) {
  if (typeof Event === "function") {
    var eventCopy = new original.constructor(original.type, original);
  } else {
    // Internet Explorer
    var eventType = original.constructor.name;
    var eventCopy = document.createEvent(eventType);
    if (original.getModifierState)
      var modifiersList = allModifiers.filter(
        original.getModifierState,
        original
      ).join(" ");
    
    if (eventType === "MouseEvent") original.initMouseEvent(
      original.type, original.bubbles, original.cancelable,
      original.view, original.detail, original.screenX, original.screenY,
      original.clientX, original.clientY, original.ctrlKey,
      original.altKey, original.shiftKey, original.metaKey,
      original.button, original.relatedTarget
    );
    if (eventType === "DragEvent") original.initDragEvent(
      original.type, original.bubbles, original.cancelable,
      original.view, original.detail, original.screenX, original.screenY,
      original.clientX, original.clientY, original.ctrlKey,
      original.altKey, original.shiftKey, original.metaKey,
      original.button, original.relatedTarget, original.dataTransfer
    );
    if (eventType === "WheelEvent") original.initWheelEvent(
      original.detail, original.screenX, original.screenY,
      original.clientX, original.clientY, original.button,
      original.relatedTarget, modifiersList,
      original.deltaX, original.deltaY, original.deltaZ, original.deltaMode
    );
    if (eventType === "PointerEvent") original.initPointerEvent(
      original.type, original.bubbles, original.cancelable,
      original.view, original.detail, original.screenX, original.screenY,
      original.clientX, original.clientY, original.ctrlKey,
      original.altKey, original.shiftKey, original.metaKey,
      original.button, original.relatedTarget,
      original.offsetX, original.offsetY, original.width, original.height,
      original.pressure, original.rotation,
      original.tiltX, original.tiltY,
      original.pointerId, original.pointerType,
      original.timeStamp, original.isPrimary
    );
    if (eventType === "TouchEvent") original.initTouchEvent(
      original.type, original.bubbles, original.cancelable,
      original.view, original.detail, original.screenX, original.screenY,
      original.clientX, original.clientY, original.ctrlKey,
      original.altKey, original.shiftKey, original.metaKey,
      original.touches, original.targetTouches, original.changedTouches,
      original.scale, original.rotation
    );
    if (eventType === "TextEvent") original.initTextEvent(
      original.type, original.bubbles, original.cancelable,
      original.view,
      original.data, original.inputMethod, original.locale
    );
    if (eventType === "CompositionEvent") original.initTextEvent(
      original.type, original.bubbles, original.cancelable,
      original.view,
      original.data, original.inputMethod, original.locale
    );
    if (eventType === "KeyboardEvent") original.initKeyboardEvent(
      original.type, original.bubbles, original.cancelable,
      original.view, original.char, original.key,
      original.location, modifiersList, original.repeat
    );
    if (eventType === "InputEvent" || eventType === "UIEvent")
      original.initUIEvent(
        original.type, original.bubbles, original.cancelable,
        original.view, original.detail
      );
    if (eventType === "FocusEvent") original.initFocusEvent(
        original.type, original.bubbles, original.cancelable,
        original.view, original.detail, original.relatedTarget
    );
  }
  
  document.getElementById(newTargetId).dispatchEvent(eventCopy);
  if (eventCopy.defaultPrevented)  newTargetId.preventDefault();
}
<button onclick="redispatchEvent(arguments[0], '2nd')">Click Here</button>
<button id="2nd" onclick="console.log('Alternate clicked!')">Alternate Button</button>

更一般的解决方案

根据您的需要,综合事件传播是比重新分发原始事件更好的解决方案。我们创建了特殊的方法来注册事件侦听器,这些事件侦听器还将这些侦听器公开给我们的代码,以便我们可以手动调用它们。确实,有一个getEventListeners函数可用于检索当前事件侦听器。但是,getEventListeners仅Chrome / Safari支持。因此,我设计了以下替代产品。尽管下面的代码看起来太大了,但是下面的代码主要是变量名,因此经过缩小后,它会很小。

/**@type{WeakMap}*/ var registeredListeners = new WeakMap();

hearEvent(document.getElementById("1st"), "click", function propagate(evt) {
  fireEvent(document.getElementById("2nd"), evt, propagate);
});

hearEvent(document.getElementById("2nd"), "click", function(evt) {
  console.log( evt.target.textContent );
});


/**
 * @param{Element} target
 * @param{string} name
 * @param{function(Event=):(boolean|undefined)} handle
 * @param{(Object<string,boolean>|boolean)=} options
 * @return {undefined}
 */
function hearEvent(target, name, handle, options) {
  target.addEventListener(name, handle, options);
  var curArr = registeredListeners.get(target);
  if (!curArr) registeredListeners.set(target, (curArr = []));
  
  curArr.push([
    "" + name,
    handle,
    typeof options=="object" ? !!options.capture : !!options,
    target
  ]);
}

/**
 * @param{Element} target
 * @param{string} name
 * @param{function(Event=):(boolean|undefined)} handle
 * @param{(Object<string,boolean>|boolean)=} options
 * @return {undefined}
 */
function muteEvent(target, name, handle, options) {
  name += "";
  target.removeEventListener(name, handle, options);
  var capturing = typeof options=="object"?!!options.capture:!!options;
  var curArr = registeredListeners.get(target);
  if (curArr)
    for (var i=(curArr.length|0)-1|0; i>=0; i=i-1|0)
      if (curArr[i][0] === name && curArr[i][2] === capturing)
        curArr.splice(i, 1);
  
  if (!curArr.length) registeredListeners.delete(target);
}

/**
 * @param{Element} target
 * @param{Event} eventObject
 * @param{Element=} caller
 * @return {undefined}
 */
function fireEvent(target, eventObject, caller) {
  var deffered = [], name = eventObject.type, curArr, listener;
  var immediateStop = false, keepGoing = true, lastTarget;
  var currentTarget = target, doesBubble = !!eventObject.bubbles;
  
  var trueObject = Object.setPrototypeOf({
    stopImmediatePropagation: function(){immediateStop = true},
    stopPropagation: function(){keepGoing = false},
    get target() {return target},
    get currentTarget() {return currentTarget}
  }, eventObject);
  
  do {
    if (curArr = registeredListeners.get(currentTarget))
      for (var i=0; i<(curArr.length|0) && !immediateStop; i=i+1|0)
        if (curArr[i][0] === name && curArr[i][1] !== caller) {
          listener = curArr[i];
          if (listener[2]) {
            listener[1].call(trueObject, trueObject);
          } else if (doesBubble || currentTarget === target) {
            deffered.push( listener );
          }
        }
    
    if (target.nodeType === 13) {
      // for the ShadowDOMv2
      deffered.push([ target ]);
      currentTarget = target = currentTarget.host;
    }
  } while (keepGoing && (currentTarget = currentTarget.parentNode));
  
  while (
    (listener = deffered.pop()) &&
    !immediateStop &&
    (lastTarget === listener[3] || keepGoing)
  )
    if (listener.length === 1) {
      // for the ShadowDOMv2
      target = listener[0];
    } else {
      lastTarget = currentTarget = listener[3];
      listener[1].call(trueObject, trueObject);
    }
}
<button id="1st">Click Here</button>
<button id="2nd">Alternate Button</button>

观察到,最小化后,所有这些代码都可以整齐地放入一个千字节(在gzip之前)。

var k=new WeakMap;m(document.getElementById("1st"),"click",function q(a){r(document.getElementById("2nd"),a,q)});m(document.getElementById("2nd"),"click",function(a){console.log(a.target.textContent)});function m(a,c,f,b){a.addEventListener(c,f,b);var d=k.get(a);d||k.set(a,d=[]);d.push([""+c,f,"object"==typeof b?!!b.capture:!!b,a])}
function r(a,c,f){var b=[],d=c.type,n=!1,p=!0,g=a,t=!!c.bubbles,l=Object.setPrototypeOf({stopImmediatePropagation:function(){n=!0},stopPropagation:function(){p=!1},get target(){return a},get currentTarget(){return g}},c);do{if(c=k.get(g))for(var h=0;h<(c.length|0)&&!n;h=h+1|0)if(c[h][0]===d&&c[h][1]!==f){var e=c[h];e[2]?e[1].call(l,l):(t||g===a)&&b.push(e)}13===a.nodeType&&(b.push([a]),g=a=g.host)}while(p&&(g=g.parentNode));for(;(e=b.pop())&&!n&&(u===e[3]||p);)if(1===e.length)a=e[0];else{var u=g=
e[3];e[1].call(l,l)}}function z(a,c,f,b){c+="";a.removeEventListener(c,f,b);f="object"==typeof b?!!b.capture:!!b;if(b=k.get(a))for(var d=(b.length|0)-1|0;0<=d;d=d-1|0)b[d][0]===c&&b[d][2]===f&&b.splice(d,1);b.length||k.delete(a)}
<button id="1st">Click Here</button>
<button id="2nd">Alternate Button</button>


非常酷的-但我认为你的第一个解决方案的最后一行应为:if (eventCopy.defaultPrevented) original.preventDefault();
sree
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.