事件监听器如何/何时在d3.js中附加?

时间:2017-01-20 16:31:54

标签: javascript d3.js svg dom-events

我正在尝试制作各种SVG编辑器。简而言之,我需要将鼠标事件附加到给定SVG内特定深度的<g>元素。由于各种原因,我无法提前知道身份证。 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");
  });

此代码有效,但在开始之前需要很长时间。让我们说我有十个这样的元素将匹配特定的选择。似乎在页面加载后的每一秒中,另外一个实际上获得了附加的鼠标事件。我想知道每次d3附加一个事件时我是否可以打印一个控制台事件,或者我怎么知道d3是否完成了附加所需的所有内容。

基本上这个JSFiddle需要更快地加载鼠标事件。如果你等几秒钟,你会看到越来越多的盒子在工作。

2 个答案:

答案 0 :(得分:5)

TL;博士

事实证明,这是臭名昭着的pointer-eventsfill麻烦的错综复杂的变化。事件处理程序实际上是立即附加到<g>元素。但是,它们在一段时间内没有执行,因为事件大多数时间都不会通过这些元素。设置pointer-events: all可以轻松解决此问题。

除了技术问题之外,这是一个完美的例子,说明为什么你应该提供一个最小的例子,其中的事情被剥离到最低限度。大量的代码使得攻击变得不必要。以下代码段包含足以演示此问题的代码:

&#13;
&#13;
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");
});
&#13;
rect {
  stroke: red;
  stroke-width: 0.2;
  stroke-dasharray: 1.5 1.5;
  fill:none;
}
&#13;
<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>
&#13;
&#13;
&#13;

分析

当浏览器确定哪个元素将成为指针事件的目标时,它将执行一个称为命中测试的事情:

  

16.5.1 Hit-testing

     

确定指针事件是否导致正命中测试取决于指针的位置,graphics element的大小和形状,以及元素上‘pointer-events’属性的计算值。

上述句子包含两个重要信息:

  1. 只有图形元素才能成为指针事件的直接目标,而仅仅<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");
    })
    

    正如您在JSFiddle中所看到的,这些将始终不同。 这与您的方案相关,因为您在组上注册了处理程序函数。这样,只有当组中的图形子元素成为指针事件的目标并且事件冒泡到组本身时,处理程序才会被执行。这本身并不是一个问题,但是,结合下一点,这就解释了为什么你的设置不起作用。

  2. pointer-events属性确定,&#34;元素是否或何时可能是鼠标事件的目标&#34; 。由于此属性从未在整个代码中设置,因此默认启动,visiblePainted定义如下(强调我的):

      

    当visibility属性设置为visible并且鼠标光标位于元素的内部(即&#39; fill&#39;)并且时,该元素只能是鼠标事件的目标。 fill属性设置为none 以外的值,或者当鼠标光标位于元素周边(即&#39; stroke&#39;)并且stroke属性设置为其他值时没有。

    正如其他人在评论中指出的那样,群组中的相关<path>元素都包含定义st8的类fill: none,从而防止它们在悬停其内部时成为事件目标,即填充。当这些路径不能成为指针事件的目标时,没有事件可能会冒泡到您的组,这会使事件侦听器无效。

    如果第一次在一个元素上执行了一个监听器(为什么会发生这种情况将在下面解释,所以暂时跟我说),这个问题通过设置{{1}来解决。路径上的属性,使其成为指针事件的合法目标。这就是为什么处理人员在他们第一次复活时会继续运作的原因。

    旁注:此效果非常强大,甚至会影响开发工具在Chrome和Firefox中处理这些元素的方式。当您尝试检查一个设置为none的元素时,通过右键单击它,开发工具将打开引用根fill元素而不是您单击的元素,因为后者不是事件的目标。相比之下,尝试使用事件处理程序已经运行的元素,可以这么说,它将为这个元素打开开发工具。

  3. 解决方案

    对此的简单解决方案是通过将属性明确设置为<svg>来允许指针事件在内部(即填充)路径上发生:

      

    当指针位于内部(即填充)或周边(即,)时,该元素只能是鼠标事件的目标。   中风)元素。 fillstrokevisibility属性的值不会影响事件处理。

    最好在注册事件处理程序之前完成,如我更新的JSFiddle

    all

    为什么它有时会起作用以及延迟的原因?

    上面提供了一个正确的分析和工作解决方案,但是,如果你给它一些时间沉入,仍然存在一个问题,为什么处理程序出现要注册,或者至少,有这样的延迟激活。关于这一点的更多思考,事实证明,理解这个问题的所有信息已经包含在我的解释中。

    正如我上面所说,d3.selectAll("svg > g > g").select("g").select("g") .attr("pointer-events", "all") .on("mouseover", function() { //... } 元素实际上是事件目标,而不是组。如果<path>属性默认为pointer-events,则指针事件并非完全无法访问,可以看到重新阅读上述规范:

      

    [...] 或当鼠标光标位于元素的周边(即“行程”)时,笔画属性设置为非none值。

    尽管臭名昭着的班级visiblePainted设置st8(显然不是没有),但它指定stroke: ff0000这是一条非常细的线。事实证明,事实证明,很难完全击中线路。但是,如果你真的打了它,它将导致路径成为事件目标,事件冒泡到组,最终执行事件处理程序。通过将stroke-width:0.24设置为更大的值可以更容易地找到路径来证明此效果:

    stroke-width

    看一下这个JSFiddle的工作演示。

    即使没有设置.st8 { fill:none; stroke:#ff0000; stroke-dasharray:1.68,1.2; stroke-linecap:round; stroke-linejoin:round; stroke-width:2 /* Set to 2 to make it easier to hit */ } ,这也行有效,因为这些线现在足够宽,可以被指针击中。因为胖线是丑陋的并且会破坏精细的布局,但这更像是一个演示而不是一个真正的解决方案。

答案 1 :(得分:3)

这是一个非常有趣的问题,我设法使它工作,但我没有解释为什么这有效。如果有深入了解的人会解释这一点,我将不胜感激。

慢速:

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");
});

快速:

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");
});

不同之处仅在于在将事件处理程序附加到元素之前对元素应用填充 - .style("fill", "white").on("mouseover",

小提琴演奏 - https://jsfiddle.net/v8e4hnff/1/

注意:还试图在SVG元素上使用JS本机选择器和事件处理程序附件实现,这比D3快得多。 IE11和Chrome上的行为相同。

如上所述,如果有人可以解释这种行为,请做!