我使用这个 D3 片段来移动 SVG g
作为 SVG 渲染顺序的其余元素顶部的元素取决于 order of elements in DOM ,并且没有 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
(如果元素是事件处理程序的有效目标,则分配您创建的特定类)。根据您的事件处理程序正在做什么以及您已经在使用监听器的祖先下元素的百分比,单个事件处理程序可以说是更好的解决方案。拥有单个监听器可能会减少事件处理的开销。但是,任何实际性能差异取决于您在事件处理程序中执行的操作以及您将在其上放置监听器的祖先子代的百分比。
对实际感兴趣的元素的事件监听器
您的问题询问您的代码已放置在要移动的元素上的监听器。鉴于您似乎并不关心由您无法控制的代码放置在元素上的监听器,那么解决此问题的蛮力方法是让您保留一个监听器列表以及放置它们的元素。
实现这种蛮力解决方法的最佳方法在很大程度上取决于您将监听器放置在元素上的方式、您使用的种类等。这是我们无法从问题中获得的所有信息。没有这些信息,就不可能对如何实现这一点做出众所周知的选择。
仅使用所有通过 selection.on()
添加的每个类型/命名空间的单个监听器:
如果每个 type.namespace 都有一个监听器,并且通过 d3.selection.on() 方法将它们全部添加,并且您没有使用 Capture 类型的监听器,那么它实际上相对容易。
当仅使用每种类型的单个监听器时,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
添加的,那么您需要实现一种记录添加到元素的监听器的通用方法。如何记录被添加为监听器的函数,您只需在原型(prototype)中添加一个方法来记录您添加为监听器的事件。例如:
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()
)。然后,您将需要一个在
appendChild
之后调用的函数。在您的 moveToFront()
.它可以包含 if 语句以仅恢复监听器 if the browser is IE11, or IE .d3.selection.prototype.restoreRecordedListeners = function () {
if(isIE) {
...
}
};
您需要选择如何存储录制的听众信息。这在很大程度上取决于您如何实现我们不知道的其他代码区域。记录元素上哪些监听器的最简单方法可能是在监听器列表中创建一个索引,然后将其记录为一个类。如果您使用的实际不同监听器函数的数量很少,则这可能是一个静态定义的列表。如果数量和种类很大,那么它可能是一个动态列表。
我可以对此进行扩展,但如何使其真正取决于您的代码。它可以简单到只保留 5-10 个用作监听器的实际不同功能。它可能需要像一个完整的通用解决方案一样健壮,以记录任何可能数量的听众。这取决于我们不了解您的代码的信息。
我希望其他人能够为您提供一个简单易用的 IE11 修复程序,您只需设置一些属性,或者调用一些方法让 IE 不删除监听器。然而,蛮力方法将解决这个问题。
关于javascript - 如果在 DOM 中移动,SVG 元素会丢失事件处理程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26019127/