Ben*_*Ben 5 javascript svg dom-events d3.js
我正在尝试制作各种SVG编辑器.长话短说,我需要将鼠标事件附加到<g>给定SVG内特定深度的元素.由于各种原因,我无法提前知道身份证.SVG非常庞大,即使不是数千个元素也会有数百个.
d3.selectAll("svg > g > g > g").select("g").on("mouseover", function() {
console.log("mouseover");
}).on("mouseout", function() {
console.log("mouseout");
}).on("click", function() {
console.log("clicked");
});
Run Code Online (Sandbox Code Playgroud)
此代码有效,但它需要很长时间才能开始.假设我有十个这样的元素将匹配特定的选择.似乎在页面加载后的每一秒中,另外一个实际上获得了附加的鼠标事件.我想知道每次d3附加一个事件时我是否可以打印一个控制台事件,或者我怎么知道d3是否完成了附加所需的一切.
事实证明,这是臭名昭著pointer-events与fill麻烦之间的错综复杂的变化。实际上,事件处理程序会立即附加到<g>元素上。但是,它们会在一段时间内不执行,因为事件在大多数情况下不会进入这些元素。设置pointer-events: all确实可以轻松解决此问题。
除了技术问题之外,这还是一个很好的例子,说明了为什么您应该提供一个最小的例子,即把事情简化到最低限度。大量的代码使不必要的攻击变得困难。以下代码片段仅包含足以说明问题的代码:
d3.select("g").on("mouseover", function() {
// The difference between below log entries shows, that the event was
// targeted at another element and bubbled up to this handler's element.
console.dir(d3.event.target.tagName); // <rect>: actual target for this event
console.dir(this.tagName); // <g>: element this handler is attached to
d3.select(this).select("rect")
.style("fill", "orange");
});Run Code Online (Sandbox Code Playgroud)
rect {
stroke: red;
stroke-width: 0.2;
stroke-dasharray: 1.5 1.5;
fill:none;
}Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v4.js"></script>
<svg width="300" height="300">
<g>
<rect x="20" y="20" width="200" height="200"/>
</g>
</svg>Run Code Online (Sandbox Code Playgroud)
当浏览器确定哪个元素将成为指针事件的目标时,它将执行称为命中测试的操作:
16.5.1命中测试
确定指针事件是否导致肯定的命中测试取决于指针的位置,图形元素的大小和形状以及元素上'pointer-events'属性的计算值。
上面的句子包含有关您的问题的两个重要信息:
仅图形元素可以成为指针事件的直接目标,而仅<g>元素本身不能单独成为这些事件的目标。但是,这些事件可能会冒出泡沫并最终到达该组。在事件处理程序中,您可以记录d3.event.target以及中引用的事件的实际目标,该目标this指向该处理程序附加到的元素:
.on("mouseover", function() {
// The difference between below log entries shows, that the event was
// targeted at another element and bubbled up to this handler's element.
console.log(d3.event.target); // <path>: actual target for this event
console.log(this); // <g>: element this handler is attached to
d3.select(this).select("path")
.style("fill", "orange");
})
Run Code Online (Sandbox Code Playgroud)
如您在此JSFiddle中所看到的,它们将始终不同。这与您的方案有关,因为您在组上注册了处理程序功能。这样,仅当组的图形子元素成为指针事件的目标且事件冒泡到组本身时,处理程序才会执行。就其本身而言,这不是什么大问题,但是结合下一点,这可以解释为什么您的设置无法正常工作。
该pointer-events属性确定“元素是否或何时成为鼠标事件的目标”。因为在整个代码中都没有设置此属性,所以默认值会插入,其visiblePainted定义如下(强调我的意思):
仅当可见性属性设置为visible且鼠标光标位于元素的内部(即“填充”)并且fill属性设置为除none之外的其他值时,该元素才可以是鼠标事件的目标。,或者当鼠标光标悬停在元素的边界(即“笔触”)上并且笔触属性设置为除none之外的其他值时。
正如其他人在评论中指出的那样,<path>您组中的所有相关元素均具有st8定义的类fill: none,从而防止它们在悬停其内部(即填充)时成为事件目标。当这些路径不能成为指针事件的目标时,就没有任何事件可能会扩展到您的组,这会使事件侦听器无用。
如果侦听器是第一次在某个元素上执行的(为什么会发生这种情况,下面对此进行说明,请耐心等待),此问题将通过fill在路径上设置该属性来解决,从而使其成为指针事件的合法目标。这就是为什么处理程序在初次出现时将继续运行的原因。
旁注:此效果非常强大,甚至会影响开发工具在Chrome和Firefox中处理这些元素的方式。当您尝试检查填充设置为none的元素时,通过右键单击它,开发工具将打开引用根<svg>元素而不是您单击的元素的参考,因为后者不是事件的目标。相比之下,可以尝试使用事件处理程序已经在工作的元素,可以这样说,它将为该元素完全打开开发工具。
一个简单的解决方案是通过将属性显式设置为,使指针事件发生在路径的内部(即,填充)all:
仅当指针位于元素的内部(即填充)或外围(即笔触)上方时,该元素才能成为鼠标事件的目标。该值
fill,stroke并且visibility属性不影响事件处理。
最好像在我更新的JSFiddle中那样在注册事件处理程序之前立即完成此操作:
d3.selectAll("svg > g > g").select("g").select("g")
.attr("pointer-events", "all")
.on("mouseover", function() {
//...
}
Run Code Online (Sandbox Code Playgroud)
上面提供了适当的分析和可行的解决方案,但是,如果您给它一些时间沉浸其中,那么仍然存在一个问题,为什么在地面上似乎已注册处理程序,或者至少在这样的延迟下激活了这些处理程序。我对此进行了更多的思考,事实证明,所有了解该问题的信息已包含在我的解释中。
如上所述,<path>元素实际上是事件目标,而不是组。使用pointer-events默认属性时,visiblePainted对于指针事件它们并不是完全不可访问的,可以重新阅读上述规范:
[…]或当鼠标光标悬停在元素的外围(即“笔触”)并且笔触属性设置为除none之外的值时。
尽管臭名昭著的类st8集stroke: ff0000(显然不是),但它指定stroke-width:0.24了一条很细的线。事实证明,如果加上破折号,根本很难达到目标。但是,如果您确实点击了它,它将导致该路径成为事件目标,并且事件冒泡到该组,最终执行事件处理程序。可以通过将设置stroke-width为更大的值来证明这一效果,使其更容易上路:
.on("mouseover", function() {
// The difference between below log entries shows, that the event was
// targeted at another element and bubbled up to this handler's element.
console.log(d3.event.target); // <path>: actual target for this event
console.log(this); // <g>: element this handler is attached to
d3.select(this).select("path")
.style("fill", "orange");
})
Run Code Online (Sandbox Code Playgroud)
看看这个JSFiddle上的工作演示。
即使没有设置,pointer-events: all它也将起作用,因为行现在足够宽,可以被指针击中。由于粗线很难看并且会破坏精细的布局,因此,这更多的是演示,而不是实际的解决方案。
这是一个非常有趣的问题,我设法使它起作用,但我没有解释为什么它起作用。如果有深入知识的人能解释这一点,我将不胜感激。
慢的:
var targetElements = d3.selectAll("svg > g > g").select("g").select("g").select("path");
targetElements.on("mouseover", function() {
d3.select(this)
.style("fill", "orange");
}).on("mouseout", function() {
d3.select(this)
.style("fill", "BLUE");
}).on("click", function() {
d3.select(this)
.style("fill", "green");
});
Run Code Online (Sandbox Code Playgroud)
快速地:
var targetElements = d3.selectAll("svg > g > g").select("g").select("g").select("path");
targetElements.style('fill', 'white'); // Black magic - comment this out and the event handler attachment is delayed alot
targetElements.on("mouseover", function() {
d3.select(this)
.style("fill", "orange");
}).on("mouseout", function() {
d3.select(this)
.style("fill", "BLUE");
}).on("click", function() {
d3.select(this)
.style("fill", "green");
});
Run Code Online (Sandbox Code Playgroud)
区别仅在于在将事件处理程序附加到元素之前对元素应用填充 -.style("fill", "white").on("mouseover",
玩的小提琴 - https://jsfiddle.net/v8e4hnff/1/
注意:还尝试在 SVG 元素上使用 JS 本机选择器和事件处理程序附件来实现,这比 D3 快一点点。IE11 和 Chrome 上的行为相同。
如上所述,如果有人可以解释这种行为,请这样做!