You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

d3.js鼠标移出时tooltip未正常隐藏、悬停时重复生成问题排查

问题根因

双tooltip出现原因

  • 图表渲染逻辑重复执行时,会多次向body中append新的tooltip节点,旧实例没有被销毁,始终停留在初始的左上角位置,只有最新的实例会跟随鼠标移动,最终呈现两个tooltip的效果
  • svg.selectAll('path')会选中svg内所有路径元素,包括坐标轴轴线、刻度线等非回归线元素,这些元素也被绑定了tooltip事件,重叠触发时会导致状态混乱
  • 未给tooltip设置pointer-events: none属性,tooltip本身会遮挡下方元素的鼠标事件,造成事件触发逻辑异常

tooltip无法隐藏原因

  • 只有最新创建的tooltip实例会被你的事件逻辑控制可见性,之前生成的旧实例没有绑定对应事件,会一直保留显示状态
  • 使用mouseout事件监听移出动作,该事件会在鼠标移入元素子节点或穿过重叠元素时提前触发,叠加事件冒泡的影响,会导致隐藏逻辑没有正确执行
修复步骤
  1. 初始化tooltip前先清理已存在的同类型节点,添加专属类名和pointer-events属性避免事件遮挡
  2. 给回归线添加专属类名,绑定事件时仅选中回归线元素,避免干扰其他路径元素
  3. 替换mouseout为mouseleave事件,新增事件冒泡阻止逻辑,避免重叠元素的事件互相干扰
  4. 给tooltip定位添加少量偏移,避免tooltip刚好遮挡鼠标位置触发异常
修改后完整代码
// 先清理已存在的tooltip,避免重复创建
d3.select("body").selectAll("div.card.tooltip").remove();
const tip = d3.select("body")
    .append("div")
    .attr("class","card tooltip")
    .style("padding", "8px")
    .style("position", "absolute")
    .style("left", 0)
    .style("top", 0)
    // 阻止tooltip自身触发鼠标事件
    .style("pointer-events", "none")
    .style("visibility", "hidden");

svg.selectAll('circle')
    .on("mouseover", (e, d) => {
        // 阻止事件冒泡,避免触发下方元素的悬停逻辑
        e.stopPropagation();
        let content = `<div class='tooltip-label'>
         ${filter_one.org}-${filter_one.type}: ${d[filter_one.org][filter_one.type]} <br/>
         ${filter_two.org}-${filter_two.type}: ${d[filter_two.org][filter_two.type]}
        </div>`;
        tip.html(content).style("visibility", "visible")
        handleMouseOver(e, d)
    }).on("mouseleave", (e, d) => {
        e.stopPropagation();
        tip.style("visibility", "hidden");
        handleMouseOut(e, d);
      })
      .on("mousemove", (e, d) => {
        e.stopPropagation();
        // 添加10px偏移,避免遮挡鼠标
        tip.style("transform", `translate(${e.pageX + 10}px,${e.pageY + 10}px)`);
      });

// 注意:生成回归线时需要给它加上regression-line类,示例:
// svg.append('path').attr('class', 'regression-line').attr('d', 回归线路径)
svg.selectAll('path.regression-line')
.on("mouseover", (e, d) => {
    e.stopPropagation();
    let x_value = Math.round(xScale.invert(e.pageX)-(spacing*2));
    let y_value = Math.round(linearRegressionLine(x_value));
    let content = `<div class='tooltip-label'>
    ${filter_one.org}-${filter_one.type}: ${x_value}
  <br/>
  ${filter_two.org}-${filter_two.type}: ${y_value}
    </div>`;
    tip.html(content).style("visibility", "visible")
    handleMouseOver(e, d)
}).on("mouseleave", (e, d) => {
    e.stopPropagation();
    tip.style("visibility", "hidden");
    handleMouseOut(e, d);
  })
  .on("mousemove", (e, d) => {
    e.stopPropagation();
    tip.style("transform", `translate(${e.pageX + 10}px,${e.pageY + 10}px)`);
  });


const handleMouseOver = (e, d) =>{
    d3.select(e.currentTarget)
      .transition()
      .duration(200)
      .attr("fill", "#334040");
}

const handleMouseOut = (e, d) => {
    d3.select(e.currentTarget)
      .transition()
      .duration(300)
      .attr("fill", "#5FA19E");
};

内容的提问来源于stack exchange,提问作者David Reke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.09 13:45:03