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

如何用JavaScript固定vis.js Network中Tooltip/Popup的位置?

如何在vis.js Network中设置固定位置的Tooltip/Popup

我太懂你的痛点了——默认的tooltip跟着鼠标乱跑,想复制里面的文本简直让人抓狂!别担心,咱们可以通过禁用默认tooltip,自己实现一个固定在节点/边中心的自定义tooltip,完美解决选中文本的问题。下面是具体的实现步骤和完整代码:

核心思路

  1. 关掉vis.js自带的tooltip,避免冲突
  2. 监听节点和边的悬停事件,精准获取它们的位置
  3. 创建一个支持文本选中的自定义tooltip,把它定位到目标元素的中心区域
  4. 监听鼠标移出或点击空白区域的事件,及时隐藏tooltip

完整代码示例

<!DOCTYPE html>
<html>
<head>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.2.0/vis.min.js"></script>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.2.0/vis.min.css" rel="stylesheet" type="text/css" />
  <style type="text/css">
    #mynetwork {
      position: relative;
      width: 600px;
      height: 400px;
      border: 1px solid lightgray;
    }
    /* 自定义tooltip样式 */
    #custom-tooltip {
      position: absolute;
      background-color: #fff;
      border: 1px solid #ccc;
      border-radius: 4px;
      padding: 8px;
      font-size: 14px;
      pointer-events: auto; /* 关键!允许鼠标选中文本 */
      z-index: 1000;
      display: none; /* 默认隐藏 */
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
  </style>
</head>
<body>
  <div id="mynetwork"></div>
  <!-- 自定义tooltip容器 -->
  <div id="custom-tooltip"></div>

  <script type="text/javascript">
    // 创建节点数组
    var nodes = new vis.DataSet([
      {id: 1, label: 'Node 1', title: '这是Node 1的固定Tooltip,可复制!'},
      {id: 2, label: 'Node 2', title: 'Node 2的内容也能轻松复制'},
      {id: 3, label: 'Node 3', title: '我固定在节点中心哦'},
      {id: 4, label: 'Node 4', title: '选我选我!'},
      {id: 5, label: 'Node 5', title: '完美解决复制问题'}
    ]);

    // 创建边数组
    var edges = new vis.DataSet([
      {from: 1, to: 3, title: '边1-3的固定Tooltip'},
      {from: 1, to: 2, title: '边1-2的内容'},
      {from: 2, to: 4, title: '边2-4的文本可复制'},
      {from: 2, to: 5, title: '我在边的中间位置'},
      {from: 3, to: 3, title: '自环边的Tooltip'}
    ]);

    // 创建network实例
    var container = document.getElementById('mynetwork');
    var data = { nodes: nodes, edges: edges };
    var options = {
      tooltip: { enabled: false } // 禁用默认tooltip,避免冲突
    };
    var network = new vis.Network(container, data, options);

    // 获取自定义tooltip元素
    const customTooltip = document.getElementById('custom-tooltip');
    const networkContainer = document.getElementById('mynetwork');

    // 监听节点悬停事件
    network.on('hoverNode', function(params) {
      const nodeId = params.node;
      const nodeData = nodes.get(nodeId);
      const nodePosition = network.getPositions(nodeId)[nodeId];
      
      // 设置tooltip内容
      customTooltip.textContent = nodeData.title;
      
      // 计算tooltip的位置(节点中心上方,避免遮挡节点)
      const tooltipWidth = customTooltip.offsetWidth;
      const tooltipHeight = customTooltip.offsetHeight;
      const left = networkContainer.offsetLeft + nodePosition.x - tooltipWidth / 2;
      const top = networkContainer.offsetTop + nodePosition.y - tooltipHeight - 10;
      
      // 定位并显示tooltip
      customTooltip.style.left = `${left}px`;
      customTooltip.style.top = `${top}px`;
      customTooltip.style.display = 'block';
    });

    // 监听边悬停事件
    network.on('hoverEdge', function(params) {
      const edgeId = params.edge;
      const edgeData = edges.get(edgeId);
      const fromNodePos = network.getPositions(edgeData.from)[edgeData.from];
      const toNodePos = network.getPositions(edgeData.to)[edgeData.to];
      
      // 计算边的中心位置
      const edgeCenterX = (fromNodePos.x + toNodePos.x) / 2;
      const edgeCenterY = (fromNodePos.y + toNodePos.y) / 2;
      
      // 设置tooltip内容
      customTooltip.textContent = edgeData.title;
      
      // 计算tooltip位置(边中心上方)
      const tooltipWidth = customTooltip.offsetWidth;
      const tooltipHeight = customTooltip.offsetHeight;
      const left = networkContainer.offsetLeft + edgeCenterX - tooltipWidth / 2;
      const top = networkContainer.offsetTop + edgeCenterY - tooltipHeight - 10;
      
      // 定位并显示tooltip
      customTooltip.style.left = `${left}px`;
      customTooltip.style.top = `${top}px`;
      customTooltip.style.display = 'block';
    });

    // 监听鼠标移出节点/边事件,隐藏tooltip
    network.on('blurNode', function() {
      customTooltip.style.display = 'none';
    });
    network.on('blurEdge', function() {
      customTooltip.style.display = 'none';
    });

    // 点击空白处也隐藏tooltip
    network.on('click', function() {
      customTooltip.style.display = 'none';
    });
  </script>
</body>
</html>

关键细节解释

  • pointer-events: auto:这是让tooltip能被鼠标选中的核心设置,默认vis tooltip的pointer-events是none,所以根本没法选中文本。
  • 位置计算逻辑:通过network.getPositions()获取节点的精确坐标,边的中心则取两端节点坐标的平均值,再微调tooltip位置避免遮挡目标元素。
  • 事件闭环处理:用hoverNode/hoverEdge触发显示,blurNode/blurEdge和点击空白处触发隐藏,确保tooltip的显示逻辑符合用户预期。

这样实现的tooltip会牢牢固定在节点或边的中心位置,你可以随意选中并复制里面的文本啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:47:33