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

如何实现Chart.js折线图的渐进式动画效果?

实现Chart.js渐进式折线动画并保留hover切换交互

问题分析

你需要实现渐进式折线动画(线条逐点绘制延伸),同时保留鼠标hover显示第二个数据集、离开隐藏的交互。之前尝试生成随机点的思路没必要,该动画的核心是通过Chart.js的自定义动画配置实现,而非增加数据点数量。你的X轴是分类标签(非数值),需要调整动画逻辑适配分类轴。

解决方案

以下是修改后的完整代码,核心调整点:

  • 移除不必要的随机点生成逻辑
  • 为图表添加自定义渐进式动画配置,适配分类X轴
  • 确保hover添加/移除数据集时,动画依然按渐进式执行
<script>
    // 原始数据
    const data1 = [
        { x: "25", y: 65 },
        { x: "40", y: 60 },
        { x: "60", y: 30 },
        { x: ">70", y: 12 }
    ];

    const data2 = [
        { x: "25", y: 65 },
        { x: "40", y: 60 },
        { x: "60", y: 35 },
        { x: ">70", y: 45 }
    ];

    // 生成分类标签
    const labels = data1.map(point => "Age " + point.x);

    // 获取画布上下文
    const ctx = document.getElementById('myChart').getContext('2d');

    // 自定义渐进式动画配置
    const totalDuration = 2000;
    const delayBetweenPoints = totalDuration / (data1.length - 1);

    // 适配分类X轴的动画逻辑:从左侧第一个点的位置开始,逐点延迟绘制
    const animationConfig = {
        x: {
            type: 'number',
            easing: 'linear',
            duration: delayBetweenPoints,
            from: (ctx) => {
                // 分类轴的第一个点位置作为初始X
                return ctx.chart.scales.x.getPixelForValue(labels[0]);
            },
            delay(ctx) {
                if (ctx.type !== 'data' || ctx.xStarted) return 0;
                ctx.xStarted = true;
                return ctx.index * delayBetweenPoints;
            }
        },
        y: {
            type: 'number',
            easing: 'linear',
            duration: delayBetweenPoints,
            from: (ctx) => {
                // 从Y轴顶部(值为100的位置)开始动画
                return ctx.chart.scales.y.getPixelForValue(100);
            },
            delay(ctx) {
                if (ctx.type !== 'data' || ctx.yStarted) return 0;
                ctx.yStarted = true;
                return ctx.index * delayBetweenPoints;
            }
        }
    };

    // 创建图表实例
    const myChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: labels,
            datasets: [
                {
                    label: 'NAD+ Level',
                    data: data1.map(point => point.y),
                    borderColor: 'white',
                    borderWidth: 2,
                    backgroundColor: 'rgba(0, 0, 0, 0)',
                    lineTension: 0.4,
                    pointBackgroundColor: 'white',
                    pointBorderColor: 'white',
                    pointBorderWidth: 1,
                    pointRadius: 0
                }
            ]
        },
        options: {
            animation: animationConfig, // 启用渐进式动画
            responsive: true,
            maintainAspectRatio: false,
            scales: {
                x: {
                    ticks: { color: 'white' },
                    grid: {
                        color: (context) => context.tick.value === 0 ? 'white' : 'transparent',
                        display: true,
                        drawTicks: false
                    }
                },
                y: {
                    beginAtZero: true,
                    ticks: { color: 'white' },
                    grid: {
                        color: (context) => context.tick.value === 0 ? 'white' : 'transparent',
                        display: true
                    }
                }
            },
            plugins: {
                legend: {
                    display: true,
                    position: 'bottom',
                    align: 'end',
                    labels: { color: 'white' }
                }
            }
        }
    });

    // 鼠标hover检测
    function isMouseOverChart(event) {
        const canvas = document.getElementById('myChart');
        const rect = canvas.getBoundingClientRect();
        const mouseX = event.clientX - rect.left;
        const mouseY = event.clientY - rect.top;
        return mouseX >= 0 && mouseX <= canvas.width && mouseY >= 0 && mouseY <= canvas.height;
    }

    // hover时添加第二个数据集(带渐进式动画)
    function renderSecondDataset(event) {
        if (isMouseOverChart(event) && myChart.data.datasets.length === 1) {
            myChart.data.datasets.push({
                label: 'NAD+ Level (After Blue Helix)',
                data: data2.map(point => point.y),
                borderColor: '#B4FFBB',
                borderWidth: 2,
                backgroundColor: 'rgba(0, 0, 0, 0)',
                lineTension: 0.4,
                pointBackgroundColor: 'white',
                pointBorderColor: 'white',
                pointBorderWidth: 1,
                pointRadius: 0
            });
            myChart.update('none'); // 先无动画更新数据,再触发渐进式动画
            myChart.update(); // 触发动画
        }
    }

    // 鼠标离开时移除第二个数据集(带渐进式反向动画)
    function removeData2(event) {
        if (!isMouseOverChart(event) && myChart.data.datasets.length > 1) {
            myChart.data.datasets.pop();
            myChart.update('none');
            myChart.update();
        }
    }

    // 绑定事件监听
    const canvas = document.getElementById('myChart');
    canvas.addEventListener('mousemove', renderSecondDataset);
    canvas.addEventListener('mouseleave', removeData2);
    window.addEventListener('resize', () => myChart.resize());
</script>

关键调整说明

  • 动画适配分类轴:针对分类X轴修改from逻辑,从第一个标签的位置开始动画,确保逐点绘制效果正确。
  • 渐进式动画核心:通过delay属性给每个数据点设置递增延迟,实现线条逐步延伸的效果;duration控制单个点的动画时长。
  • 数据集切换优化:添加/移除数据集时先执行无动画更新(update('none')),再触发动画,避免数据突变导致的异常动画。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 13:34:51