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

React中Chart.js外部Tooltip设置垂直滚动失效问题求助

在React中为Chart.js实现可滚动外部Tooltip的问题

我需要在React中给Chart.js的Tooltip添加垂直滚动功能,因为要展示大量数据。已经参考官方文档创建了外部HTML Tooltip,并且给tooltipEl添加了max-height和overflow-y样式,但这些样式没有生效,想问下是不是没办法实现Tooltip垂直滚动?

const getOrCreateTooltip = (chart: any) => {
  let tooltipEl = chart.canvas.parentNode.querySelector("div");

  if (!tooltipEl) {
    tooltipEl = document.createElement("div");
    tooltipEl.style.background = "rgba(0, 0, 0, 0.7)";
    tooltipEl.style.borderRadius = "3px";
    tooltipEl.style.color = "white";
    tooltipEl.style.opacity = 1;
    tooltipEl.style.pointerEvents = "none";
    tooltipEl.style.position = "absolute";
    tooltipEl.style.transform = "translate(-50%, 0)";
    tooltipEl.style.transition = "all .1s ease";

    const table = document.createElement("table");
    table.style.margin = "0px";

    tooltipEl.appendChild(table);
    chart.canvas.parentNode.appendChild(tooltipEl);
  }

  return tooltipEl;
};

const externalTooltipHandler = (context: any) => {
  // Tooltip Element
  const { chart, tooltip } = context;
  const tooltipEl = getOrCreateTooltip(chart);

  // Hide if no tooltip
  if (tooltip.opacity === 0) {
    tooltipEl.style.opacity = 0;
    return;
  }

  // Set Text
  if (tooltip.body) {
    const titleLines = tooltip.title || [];
    const bodyLines = tooltip.body.map((b: any) => b.lines);

    const tableHead = document.createElement("thead");

    titleLines.forEach((title: any) => {
      const tr = document.createElement("tr");
      tr.style.borderWidth = "0";

      const th = document.createElement("th");
      th.style.borderWidth = "0";
      const text = document.createTextNode(title);

      th.appendChild(text);
      tr.appendChild(th);
      tableHead.appendChild(tr);
    });

    const tableBody = document.createElement("tbody");
    bodyLines.forEach((body: any, i: any) => {
      const colors = tooltip.labelColors[i];

      const span = document.createElement("span");
      span.style.background = colors.backgroundColor;
      span.style.borderColor = colors.borderColor;
      span.style.borderWidth = "2px";
      span.style.marginRight = "10px";
      span.style.height = "10px";
      span.style.width = "10px";
      span.style.display = "inline-block";

      const tr = document.createElement("tr");
      tr.style.fontSize = "14px";
      tr.style.backgroundColor = "inherit";
      tr.style.borderWidth = "0";

      const td = document.createElement("td");
      td.style.borderWidth = "0";

      const text = document.createTextNode(body);

      td.appendChild(span);
      td.appendChild(text);
      tr.appendChild(td);
      tableBody.appendChild(tr);
    });

    const tableRoot = tooltipEl.querySelector("table");

    while (tableRoot.firstChild) {
      tableRoot.firstChild.remove();
    }

    tableRoot.appendChild(tableHead);
    tableRoot.appendChild(tableBody);
  }

  const { offsetLeft: positionX, offsetTop: positionY } = chart.canvas;

  tooltipEl.style.opacity = 1;
  tooltipEl.style.left = positionX + tooltip.caretX + "px";
  tooltipEl.style.top = positionY + tooltip.caretY + "px";
  tooltipEl.style.font = tooltip.options.bodyFont.string;
  tooltipEl.style.padding =
    tooltip.options.padding + "px " + tooltip.options.padding + "px";

  tooltipEl.style.maxHeight = "330px";
  tooltipEl.style.overflowY = "auto";
  tooltipEl.style.zIndex = 1000;
};

const options = {
  maintainAspectRatio: false,
  scales: {
    x: {
      offset: true,
      grid: {
        drawTicks: false,
        offset: true,
        color: "#00001A26",
      },
      ticks: {
        color: "rgba(0 ,0 ,0 ,0.7)",
        padding: 4,
        stepSize: 1,
        font: {
          size: 12,
        },
      },
      border: {
        dash: [2, 2],
      },
      min: -3,
      max: 3,
    },
    y: {
      beginAtZero: true,
      grid: {
        drawTicks: false,
        color: "#00001A26",
      },
      ticks: {
        color: "rgba(0 ,0 ,0 ,0.7)",
        padding: 4,
        maxTicksLimit: 5,
        font: {
          size: 12,
        },
      },
      border: {
        dash: [2, 2],
        display: false,
      },
    },
  },
  plugins: {
    tooltip: {
      enabled: false,
      external: externalTooltipHandler,
    },
  },
};

解决方法

你的问题主要有两个核心原因:

  1. pointerEvents: none 会阻止鼠标在Tooltip上触发滚动事件,导致滚动交互失效
  2. 内部的<table>元素默认布局会直接撑开Tooltip容器,让外层的max-height限制不生效

修改步骤:

  1. 启用鼠标交互:将tooltipEl的pointerEvents从none改为auto,让鼠标能在Tooltip上触发滚动
  2. 限制表格高度:给内部的table设置display: block和max-height,让表格内容在超出时滚动,同时不撑开外层容器

修改后的代码片段:

调整getOrCreateTooltip函数:

const getOrCreateTooltip = (chart: any) => {
  let tooltipEl = chart.canvas.parentNode.querySelector("div");

  if (!tooltipEl) {
    tooltipEl = document.createElement("div");
    tooltipEl.style.background = "rgba(0, 0, 0, 0.7)";
    tooltipEl.style.borderRadius = "3px";
    tooltipEl.style.color = "white";
    tooltipEl.style.opacity = 1;
    // 允许鼠标交互,才能触发滚动
    tooltipEl.style.pointerEvents = "auto";
    tooltipEl.style.position = "absolute";
    tooltipEl.style.transform = "translate(-50%, 0)";
    tooltipEl.style.transition = "all .1s ease";
    tooltipEl.style.maxHeight = "330px";
    tooltipEl.style.overflowY = "auto";
    tooltipEl.style.zIndex = 1000;

    const table = document.createElement("table");
    table.style.margin = "0px";
    // 让表格支持滚动,高度减去上下padding值
    table.style.display = "block";
    table.style.maxHeight = `calc(100% - ${tooltip.options.padding * 2}px)`;
    table.style.overflowY = "auto";
    // 可选:优化滚动条样式
    table.style.scrollbarWidth = "thin";
    table.style.scrollbarColor = "#fff rgba(0,0,0,0.3)";

    tooltipEl.appendChild(table);
    chart.canvas.parentNode.appendChild(tooltipEl);
  }

  return tooltipEl;
};

简化externalTooltipHandler中的重复样式:

移除已经在创建阶段设置过的重复样式:

// 移除以下重复代码
// tooltipEl.style.maxHeight = "330px";
// tooltipEl.style.overflowY = "auto";
// tooltipEl.style.zIndex = 1000;

修改完成后,Tooltip就能正常实现垂直滚动,同时不会影响Chart.js的其他交互(比如鼠标悬停高亮)。


内容的提问来源于stack exchange,提问作者Ji Yeon Kim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 19:17:06