Javascript 如果在DOM中移动,SVG元素将丢失事件处理程序

Javascript 如果在DOM中移动,SVG元素将丢失事件处理程序,javascript,html,internet-explorer,svg,d3.js,Javascript,Html,Internet Explorer,Svg,D3.js,我使用此D3片段将SVGg元素移动到rest元素的顶部,因为SVG呈现顺序取决于,并且没有z索引: d3.selection.prototype.moveToFront = function () { return this.each(function () { this.parentNode.appendChild(this); }); }; 我像这样运行它: d3.select(el).moveToFront() 我的问题是,如果我添加一个D3事件监听器,比如D3.sel

我使用此D3片段将SVG
g
元素移动到rest元素的顶部,因为SVG呈现顺序取决于,并且没有z索引:

d3.selection.prototype.moveToFront = function () {
  return this.each(function () {
    this.parentNode.appendChild(this);
  });
};
我像这样运行它:

d3.select(el).moveToFront()
我的问题是,如果我添加一个D3事件监听器,比如
D3.select(el).on('mouseleave',function(){})
,然后使用上面的代码将元素移动到DOM树的前面,所有事件监听器在Internet Explorer 11中都会丢失,在其他浏览器中仍然可以正常工作。
如何解决此问题?

父元素上的单事件侦听器,或更高的DOM祖先:

有一个相对简单的解决方案,我最初没有提到,因为我假设你认为它在你的情况下不可行。该解决方案是,不是在单个子元素上每个都有多个侦听器,而是在一个祖先元素上有一个侦听器,该祖先元素为其子元素上的某一类型的所有事件调用该侦听器。它可以设计为基于
event.target
event.target.id
,或者更好的是,基于
event.target.className
(如果元素是事件处理程序的有效目标,则指定创建的特定类)快速选择进一步处理。根据您的事件处理程序正在做什么以及您已经在使用侦听器的祖先下的元素百分比,单个事件处理程序可以说是更好的解决方案。拥有一个侦听器可能会减少事件处理的开销。但是,任何实际的性能差异都取决于您在事件处理程序中所做的操作,以及您本来会放置侦听器的祖先的子级的百分比

实际感兴趣元素的事件侦听器

您的问题询问您的代码在被移动的元素上放置了哪些侦听器。假设您似乎不关心由您不控制的代码放置在元素上的侦听器,那么解决这个问题的暴力方法是为您保留一个侦听器列表以及放置它们的元素

实现此暴力解决方案的最佳方法在很大程度上取决于您将侦听器放置在元素上的方式、您使用的种类等。这些都是我们从问题中无法获得的信息。如果没有这些信息,就不可能对如何实现这一点做出一个众所周知的好选择

仅使用每种类型/命名空间的单个侦听器,所有侦听器都是通过以下方式添加的:

如果每个type.namespace都有一个监听器,并且通过d3.selection.on()方法添加了所有监听器,并且没有使用捕获类型监听器,那么这实际上是相对容易的

当只使用每种类型的单个侦听器时,
selection.on()
方法允许您读取分配给元素和类型的侦听器

因此,您的
moveToFront()
方法可能会变成:

var isIE = /*@cc_on!@*/false || !!document.documentMode; // At least IE6
var typesOfListenersUsed = [ "click", "command", "mouseover", "mouseleave", ...];

d3.selection.prototype.moveToFront = function () {
  return this.each(function () {
    var currentListeners={};
    if(isIE) {
      var element = this;
      typesOfListenersUsed.forEach(function(value){
         currentListeners[value] = element.selection.on(value);
      });
    }
    this.parentNode.appendChild(this);
    if(isIE) {
      typesOfListenersUsed.forEach(function(value){
         if(currentListeners[value]) { 
           element.selection.on(value, currentListeners[value]);
         }
      });
    }
  });
};
您不一定需要检查IE,因为在其他浏览器中重新放置侦听器不会有什么坏处。然而,这需要时间,最好不要这样做

即使使用同一类型的多个侦听器,您也应该能够通过在侦听器列表中指定名称空间来使用它。例如:

var typesOfListenersUsed = [ "click", "click.foo", "click.bar"
                            , "command", "mouseover", "mouseleave", ...];
d3.selection.prototype.recOn = function (type, func) {
  recordEventListener(this, type, func);
  d3.select(this).on(type,func);
};
常规,相同类型的多个侦听器:

如果您使用的侦听器不是通过
d3
添加的,那么您需要实现记录添加到元素的侦听器的通用方法

如何记录作为侦听器添加的函数,只需向原型添加一个方法,该方法记录作为侦听器添加的事件。例如:

var typesOfListenersUsed = [ "click", "click.foo", "click.bar"
                            , "command", "mouseover", "mouseleave", ...];
d3.selection.prototype.recOn = function (type, func) {
  recordEventListener(this, type, func);
  d3.select(this).on(type,func);
};
然后使用
d3.select(el).recOn('mouseleave',function(){})
代替
d3.select(el).on('mouseleave',function(){})

如果您使用的是通用解决方案,因为您不是通过
d3
添加一些侦听器,那么您需要添加函数来包装调用,无论您如何添加侦听器(例如
addEventListener()

然后需要在
moveToFront()
中的
appendChild
之后调用一个函数。它可以包含只还原侦听器的if语句

您需要选择如何存储录制的侦听器信息。这在很大程度上取决于您如何实现代码中我们不知道的其他方面。记录元素上哪些侦听器的最简单方法可能是在侦听器列表中创建索引,然后将其记录为类。如果实际使用的不同侦听器函数的数量很小,则这可能是一个静态定义的列表。如果数量和种类很大,那么它可能是一个动态列表

我可以对此进行扩展,但它的健壮性实际上取决于您的代码。它可以简单到只保留5-10个实际不同的函数作为侦听器。它可能需要具有足够的健壮性,以便成为记录任何可能数量的侦听器的完整通用解决方案。这取决于我们对您的代码不了解的信息


我希望其他人能够为您提供一个简单易行的IE11修复程序,您只需设置一些属性,或者调用一些方法来让IE不丢弃侦听器。但是,蛮力方法可以解决这个问题。

一个解决方案可以是使用事件委派。这个相当简单的范例在jQuery中很常见(这让我想到在这里尝试它)

通过使用委派事件侦听器扩展
d3.selection
原型,我们可以侦听父元素上的事件,但只有当事件的目标也是我们所需的目标时,才应用处理程序

因此,不是:

d3.select('#targetElement').on('mouseout',function(){})
您可以使用:

d3.select('#targetElementParent').delegate('mouseout','#targetElement',function(){})
现在,移动元素时事件是否丢失,或者添加/编辑/删除事件都无关紧要
// myElements is a d3 selection of, for example, circles that overlap each other
myElements.on('mouseover', function(hoveredDatum) {
  // On mouseover, the currently hovered element is sorted to the front by creating
  // a custom comparator function that returns “1” for the hovered element and “0”
  // for all other elements to not affect their sort order.
  myElements.sort(function(datumA, datumB) {
    return (datumA === hoveredDatum) ? 1 : 0;
  });
});