如何实现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
相关产品推荐
相关产品推荐

