首页 > 解决方案 > 在两个重叠元素上捕获鼠标悬停事件

问题描述

所以我有一个 d3 图表,上面有一个rect覆盖来保存mouseover事件的十字准线元素。在叠加层下,我还有其他显示具有mouseover事件处理程序的数据的矩形,但叠加层阻止mouseover了下面子矩形上的触发事件。

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

具有CrosshairWindowCSS 属性pointer-events: all。如果我删除它,我会让我的事件在.candleStickWindow但不是CrosshairWindow. 如何在两个元素上获取鼠标事件?

谢谢你的帮助!

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

在此处输入图像描述

标签: d3.jssvgmouseevent

解决方案


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

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

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

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

然后,您可以使用以下实现的接口方法将新创建的事件对象分派到您的十字准线rect元素:.dispatchEvent()EventTargetSVGRectElement

.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>


推荐阅读