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

Chart.js鼠标快速移出时十字线未消失问题及解决需求

解决Chart.js十字线快速移出不消失的问题

我使用Chart.js 4.4.0实现类似Flot.js的十字线效果,参考相关指南编写了代码,能实现鼠标悬停时显示十字线,但存在问题:鼠标快速移出图表区域时,十字线常卡在边缘无法消失,缓慢移出则正常,即使将事件监听器绑定到图表容器也无法解决。需要确保光标在图表区域外时十字线必定消失的适配方案。

原代码

<html>
  <head>
    <script type="text/javascript" src="../include/chart-4.4.0.umd.js"></script>
  </head>
  <body>

    <div id="chart_container" class="plot" style="width:50%;">
      <div>
        <canvas id="myChart"></canvas>
      </div>
    </div>


    <script>
      /* The Chart.js context */
      const ctx = document.getElementById('myChart');

      /* The Chart container node, used for crosshair */
      let container = document.getElementById('chart_container');

      let chart_data = {
          datasets: [{
              data: [{x:0, y:1}, {x:1, y:1}, {x:1, y:2}, {x:2, y:2},
                     {x:2, y:3}, {x:3, y:3}, {x:3, y:1}, {x:4, y:1}],
              borderWidth: 3,
              /* For the crosshair to work, we must disable other
               * tooltip hover effects: */
              pointHoverRadius: 0,
              pointHitRadius: 0,
              pointRadius: 0
          }]
      };

      /* Chart.js chart options object for the Chart.js
       *  constructor, defined below. */
      let chart_options = {
          showLine: true,
          pointStyle: false,
          borderColor: 'rgba(255,204,0,1)',
          borderWidth: 1,
          fill: '+1',
          scales: {
              y: {beginAtZero: true}
          },
      };

      /* Create the Chart.js chart */
      let cjs_plot = new Chart(ctx, {
          type: 'scatter',
          data: chart_data,
          options: chart_options,
      });

      /* Implementing the crosshair effect previously done
       * using the Flot.js 'crosshair' plugin. */
      container.addEventListener('mousemove', (e) => {
          crosshair(cjs_plot, e);
      })
      
      function crosshair(chart, mousemove) {

          /* Setting `update` to 'none' means the refresh is
           * invisible so that we don't see previous crosshair
           * lines as the mousemoves. */
          chart.update('none');

          //console.log(mousemove);
          const x = mousemove.offsetX;
          const y = mousemove.offsetY;

          const { ctx, chartArea: {top, bottom, left, right, width, height} } = chart;
          ctx.save();

          /* Draw items */
          /* rgba(255,0,0,1) is solid red */
          ctx.strokeStyle = 'rgba(255,0,0,1)';
          ctx.lineWidth = 1;

          /* Only draw crosshairs if mouse is inside the chart area.
           * Remember 'y' is measured top-down. */
          if(x >= left && x <= right && y >= top && y <= bottom) {

              /* Draw horizontal line */
              ctx.beginPath();
              ctx.moveTo(left, y);
              ctx.lineTo(right, y);
              ctx.stroke();
              ctx.closePath();

              /* Draw vertical line */
              ctx.beginPath();
              ctx.moveTo(x, top);
              ctx.lineTo(x, bottom);
              ctx.stroke();
              ctx.closePath();

          }
          ctx.restore();
      } // End of crosshair() function

    </script>
  </body>
</html>

解决方案

问题根源是鼠标快速移出时,可能直接跳过容器边缘,导致最后一次mousemove事件的坐标仍被判定在图表区域内,或者完全没触发容器的mousemove事件。通过以下两点修改解决:

  1. 新增mouseleave事件监听,当鼠标离开容器时强制清除十字线
  2. 优化十字线绘制逻辑,确保每次绘制前都基于图表原始状态重绘

修改后的完整代码

<html>
  <head>
    <script type="text/javascript" src="../include/chart-4.4.0.umd.js"></script>
  </head>
  <body>

    <div id="chart_container" class="plot" style="width:50%;">
      <div>
        <canvas id="myChart"></canvas>
      </div>
    </div>


    <script>
      const ctx = document.getElementById('myChart');
      let container = document.getElementById('chart_container');

      let chart_data = {
          datasets: [{
              data: [{x:0, y:1}, {x:1, y:1}, {x:1, y:2}, {x:2, y:2},
                     {x:2, y:3}, {x:3, y:3}, {x:3, y:1}, {x:4, y:1}],
              borderWidth: 3,
              pointHoverRadius: 0,
              pointHitRadius: 0,
              pointRadius: 0
          }]
      };

      let chart_options = {
          showLine: true,
          pointStyle: false,
          borderColor: 'rgba(255,204,0,1)',
          borderWidth: 1,
          fill: '+1',
          scales: {
              y: {beginAtZero: true}
          },
      };

      let cjs_plot = new Chart(ctx, {
          type: 'scatter',
          data: chart_data,
          options: chart_options,
      });

      // 同时监听mousemove和mouseleave事件
      container.addEventListener('mousemove', (e) => {
          crosshair(cjs_plot, e);
      });
      
      container.addEventListener('mouseleave', () => {
          // 鼠标离开时,强制更新图表清除十字线
          cjs_plot.update('none');
      });
      
      function crosshair(chart, mousemove) {
          // 每次绘制前先更新图表,清除之前的十字线
          chart.update('none');

          const x = mousemove.offsetX;
          const y = mousemove.offsetY;

          const { ctx, chartArea: {top, bottom, left, right} } = chart;
          ctx.save();

          ctx.strokeStyle = 'rgba(255,0,0,1)';
          ctx.lineWidth = 1;

          if(x >= left && x <= right && y >= top && y <= bottom) {
              // 绘制水平十字线
              ctx.beginPath();
              ctx.moveTo(left, y);
              ctx.lineTo(right, y);
              ctx.stroke();
              ctx.closePath();

              // 绘制垂直十字线
              ctx.beginPath();
              ctx.moveTo(x, top);
              ctx.lineTo(x, bottom);
              ctx.stroke();
              ctx.closePath();
          }
          ctx.restore();
      }
    </script>
  </body>
</html>

关键修改说明

  • 新增mouseleave事件监听器:当鼠标离开容器区域时,直接调用chart.update('none')强制重绘图表,清除残留的十字线
  • 保持mousemove事件的逻辑不变,但确保每次绘制十字线前都先重置图表状态,避免旧的十字线残留

这样无论鼠标是快速还是缓慢移出,只要离开容器范围,十字线都会被立即清除。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 18:24:50