d3.js鼠标移出时tooltip未正常隐藏、悬停时重复生成问题排查
问题根因
双tooltip出现原因
- 图表渲染逻辑重复执行时,会多次向body中append新的tooltip节点,旧实例没有被销毁,始终停留在初始的左上角位置,只有最新的实例会跟随鼠标移动,最终呈现两个tooltip的效果
svg.selectAll('path')会选中svg内所有路径元素,包括坐标轴轴线、刻度线等非回归线元素,这些元素也被绑定了tooltip事件,重叠触发时会导致状态混乱- 未给tooltip设置
pointer-events: none属性,tooltip本身会遮挡下方元素的鼠标事件,造成事件触发逻辑异常
tooltip无法隐藏原因
- 只有最新创建的tooltip实例会被你的事件逻辑控制可见性,之前生成的旧实例没有绑定对应事件,会一直保留显示状态
- 使用
mouseout事件监听移出动作,该事件会在鼠标移入元素子节点或穿过重叠元素时提前触发,叠加事件冒泡的影响,会导致隐藏逻辑没有正确执行
修复步骤
- 初始化tooltip前先清理已存在的同类型节点,添加专属类名和
pointer-events属性避免事件遮挡 - 给回归线添加专属类名,绑定事件时仅选中回归线元素,避免干扰其他路径元素
- 替换
mouseout为mouseleave事件,新增事件冒泡阻止逻辑,避免重叠元素的事件互相干扰 - 给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
相关产品推荐
相关产品推荐

