在两个重叠元素上捕获鼠标悬停事件

Capturing mouseover events on two overlapping elements

所以我有一个带有 rect 叠加层的 d3 图表,用于在 mouseover 事件上保留十字准线元素。在覆盖层下,我还有其他 rects 显示也有 mouseover 事件处理程序的数据,但是覆盖层阻止 mouseover 事件触发下面的子 rects。

let chartWindow = svg
  .append("g");

/* this holds axis groups, and cadlestick group*/
let candleStickWindow = chartWindow.append("g")
  //this event never fires
  .on('mousemove', ()=>console.log('mouse move'));

let candlesCrosshairWindow = chartWindow
  .append("rect")
  .attr("class", "overlay")

  .attr("height", innerHeight)
  .attr("width", innerWidth)
  .on("mouseover", function() {
    crosshair.style("display", null);
  })
  .on("mouseout", function() {
    crosshair.style("display", "none");
    removeAllAxisAnnotations();
  })
  .on("mousemove", mousemove);

CrosshairWindow有CSS属性pointer-events: all。如果我删除它,我的事件将在 candleStickWindow 而不是 CrosshairWindow 上触发。如何让鼠标事件同时出现在两个元素上??

感谢您的帮助!

更新 我将十字准线矩形元素更改为位于底部,它有点管用,烛台条鼠标悬停事件有效但它阻止了十字准线工作。

想到的一个解决方案可能使用 event bubbling,但是,只有当事件可以沿着相同的 DOM 子树冒泡时才有效。如果在您的 DOM 结构中,十字准线矩形和其他元素不共享您可以 合理地 附加此类侦听器的共同祖先,则您需要重新考虑您的 DOM 或诉诸其他解决方案。对于这个答案,我将提出一种更普遍适用的替代方法。

您可以将全尺寸 rect 放在 SVG 的最底部,并将其 pointer-events 设置为 all。这样你就可以很容易地附加一个 mousemove 处理程序来控制你的十字准线在整个视口中的移动。但是,正如您自己注意到的那样,如果上面的元素具有附加到该特定事件类型的侦听器,则此方法不起作用。因为在那种情况下,一旦事件到达其目标,就无法将其进一步传播到用于处理十字准线组件的底层矩形。不过,解决方法很简单,因为您可以克隆该事件并将新事件直接发送到您的矩形。

通过使用 MouseEvent() 构造函数从 d3.event 引用传入事件的详细信息来完成事件的克隆:

new MouseEvent(d3.event.type, d3.event)

然后您可以使用 .dispatchEvent() method of the EventTarget interface which is implemented by SVGRectElement:

将新创建的事件对象分派到您的十字准线 rect 元素
.dispatchEvent(new MouseEvent(d3.event.type, d3.event));

由于您的问题中缺少完整的示例,我自己设置了一个工作演示来说明该方法。您可以在蓝色圆圈周围拖动,这是十字准线组件的简化版本。请注意,即使在橙色矩形下方,圆圈也可以无缝移动。为了演示附加到这些小矩形的事件处理程序,当使用鼠标指针进入或离开它们时,它们将转换为绿色并返回橙色。

const width = 500;
const height = 500;
const radius = 10;
const orange = d3.hsl("orange");
const steelblue = d3.hsl("steelblue");
const limegreen = d3.hsl("limegreen");

const svg = d3.select("body")
  .append("svg")
    .attr("width", width)
    .attr("height", height);
    
const target = svg.append("rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", width)
    .attr("height", height)
    .attr("fill", "none")
    .attr("pointer-events", "all")
    .on("mousemove", () => {
      circle.attr("cx", d3.event.clientX - radius);
      circle.attr("cy", d3.event.clientY - radius);
    });

const circle = svg.append("circle")
    .attr("r", radius)
    .attr("fill", steelblue)
    .attr("pointer-events", "none");
    
const rect = svg.selectAll(null)
  .data(d3.range(3).map(d => [Math.random() * width, Math.random() * height]))
  .enter().append("rect")
    .attr("x", d => d[0])
    .attr("y", d => d[1])
    .attr("width", 50)
    .attr("height", 50)
    .attr("fill", orange)
    .attr("opacity", 0.5)
    .on("mouseover", function() { 
      d3.select(this).transition().attr("fill", limegreen);
    })
    .on("mousemove", function() { 
      target.node().dispatchEvent(new MouseEvent(d3.event.type, d3.event));
    })
    .on("mouseout", function() { 
      d3.select(this).transition().attr("fill", orange);
    });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>