Chart.js 动画进度条实战:使用 animation 回调同步外部进度 UI
2026/9/18 3:50:36 网站建设 项目流程

Chart.js 动画进度条实战:使用 animation 回调同步外部进度 UI

【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js

导读

本文以 Chart.js 官方示例 progress-bar.md 为核心,讲解如何利用options.animation下的onProgressonComplete回调,把图表动画的实时进度同步到页面上的原生<progress>进度条。你将掌握动画回调的完整参数语义(currentStepnumStepsinitial)、Chart.js 内部动画驱动机制(core.animator.js 的 progress/complete 事件分发),以及如何把官方示例改写成脱离文档工具链、可直接落地的独立实现。

示例概览:用两条进度条区分"首次动画"与"后续动画"

Chart.js 默认所有图表创建和更新都带动画。官方 advanced 示例在页面顶部放置了两个原生<progress>元素:

<progress id="initialProgress" max="1" value="0" style="width: 100%"></progress> <progress id="animationProgress" max="1" value="0" style="width: 100%"></progress>
  • initialProgress:反映图表首次创建时的初始动画进度;
  • animationProgress:反映每次chart.update()触发的后续动画进度。

由于两个进度条的max="1",回调中只需把"已完成步数 / 总步数"的比值赋给value即可。

完整示例代码剖析

官方示例由三个代码块构成:actions(页面操作按钮逻辑)、setup(数据与 DOM 引用)、config(图表配置)。以下为完整代码(示例中// <block:...>注释是文档构建工具的分块标记,实际项目中可去掉)。

1. 操作按钮(actions 块)

const actions = [ { name: 'Randomize', handler(chart) { chart.data.datasets.forEach(dataset => { dataset.data = Utils.numbers({count: chart.data.labels.length, min: -100, max: 100}); }); chart.update(); } }, { name: 'Add Dataset', handler(chart) { const data = chart.data; const dsColor = Utils.namedColor(chart.data.datasets.length); const newDataset = { label: 'Dataset ' + (data.datasets.length + 1), backgroundColor: Utils.transparentize(dsColor, 0.5), borderColor: dsColor, data: Utils.numbers({count: data.labels.length, min: -100, max: 100}), }; chart.data.datasets.push(newDataset); chart.update(); } }, { name: 'Add Data', handler(chart) { const data = chart.data; if (data.datasets.length > 0) { data.labels = Utils.months({count: data.labels.length + 1}); for (let index = 0; index < data.datasets.length; ++index) { data.datasets[index].data.push(Utils.rand(-100, 100)); } chart.update(); } } }, { name: 'Remove Dataset', handler(chart) { chart.data.datasets.pop(); chart.update(); } }, { name: 'Remove Data', handler(chart) { chart.data.labels.splice(-1, 1); // remove the label first chart.data.datasets.forEach(dataset => { dataset.data.pop(); }); chart.update(); } } ];

需要说明的是,actions数组本身不是 Chart.js 的 API,而是文档站通过插件把每个name转成按钮并绑定onClick监听器。Utils.numbersUtils.monthsUtils.randUtils.namedColorUtils.transparentize等辅助函数定义在 docs/scripts/utils.js,仅供文档示例生成随机数据使用(例如rand采用可播种的线性同余伪随机算法,numbers支持min/max/count/continuity等参数),并不随库发布,官方明确提示不要在生产环境依赖该文件。在自己项目里用数组字面量或自己的数据生成逻辑替换即可。

2. 数据与 DOM 引用(setup 块)

const initProgress = document.getElementById('initialProgress'); const progress = document.getElementById('animationProgress'); const DATA_COUNT = 7; const NUMBER_CFG = {count: DATA_COUNT, min: -100, max: 100}; const labels = Utils.months({count: 7}); const data = { labels: labels, datasets: [ { label: 'Dataset 1', data: Utils.numbers(NUMBER_CFG), borderColor: Utils.CHART_COLORS.red, backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5), }, { label: 'Dataset 2', data: Utils.numbers(NUMBER_CFG), borderColor: Utils.CHART_COLORS.blue, backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5), } ] };

两个数据集分别使用红蓝配色(Utils.CHART_COLORS.redrgb(255, 99, 132)bluergb(54, 162, 235)),数据范围 -100 ~ 100,x 轴为 7 个月份标签(对应 docs/general/data-structures.md 中的labels结构)。

3. 图表配置(config 块)——核心所在

const config = { type: 'line', data: data, options: { animation: { duration: 2000, onProgress: function(context) { if (context.initial) { initProgress.value = context.currentStep / context.numSteps; } else { progress.value = context.currentStep / context.numSteps; } }, onComplete: function(context) { if (context.initial) { console.log('Initial animation finished'); } else { console.log('animation finished'); } } }, interaction: { mode: 'nearest', axis: 'x', intersect: false }, plugins: { title: { display: true, text: 'Chart.js Line Chart - Animation Progress Bar' } }, }, };

关键点:

  • animation.duration: 2000把动画时长设置为 2000ms(默认值为 1000ms,见 core.animations.defaults.js 中的duration: 1000)。
  • onProgress在动画的每一帧被调用,这里根据context.initial决定更新哪条进度条。
  • onComplete在动画全部结束时被调用,示例仅用于打印日志,实际可替换为任何"动画结束"后的联动逻辑。
  • interactionplugins.title与动画进度无关,属于示例图表本身的交互与标题配置。

动画回调核心:onProgress / onComplete 与回调上下文对象

onProgressonComplete只能配置在主动画配置options.animation下(见 docs/configuration/animations.md 的 Animation Callbacks 一节)。两者接收同一个回调参数对象,字段语义如下:

字段类型说明
chartChart当前图表实例
currentStepnumber当前动画已进行的步数(毫秒计)
numStepsnumber本次动画开始时动画的总步数(总毫秒数)
initialboolean是否为图表的首次(初始)动画

进度比值即currentStep / numSteps,取值在 0 ~ 1 之间,正好对应<progress>元素的value语义。该对象结构同时记载于 docs/configuration/animations.md,示例代码与文档完全一致:

{ // Chart object chart: Chart, // Number of animations still in progress currentStep: number, // `true` for the initial animation of the chart initial: boolean, // Total number of animations at the start of current animation numSteps: number, }

一个最简的进度条同步实现(不区分首次/后续):

const chart = new Chart(ctx, { type: 'line', data: data, options: { animation: { onProgress: function(animation) { progress.value = animation.currentStep / animation.numSteps; } } } });

源码级原理:Animator 如何驱动 progress / complete 事件

要理解currentStepnumStepsinitial从何而来,需要看两个核心文件。

Animator 的_notify与事件分发

src/core/core.animator.js 中维护了一个全局单例Animatorexport default new Animator()),它通过requestAnimFrame驱动的_update循环逐帧推进所有图表的动画。每次绘制完成后会触发progress事件,全部动画项结束后触发complete事件:

_notify(chart, anims, date, type) { const callbacks = anims.listeners[type]; const numSteps = anims.duration; callbacks.forEach(fn => fn({ chart, initial: anims.initial, numSteps, currentStep: Math.min(date - anims.start, numSteps) })); }

可见:

  • numSteps等于anims.duration,即本次动画的总时长(毫秒)。
  • currentStep等于当前时间戳减去动画开始时间戳,并Math.min钳制到不超过numSteps,保证进度比不会超过 1。
  • initial取自anims.initial,在动画全部完成(_notify(..., 'complete'))之后会被置为falseanims.initial = false),所以只有图表第一次渲染时的动画会带着initial: true

_update中,每个动画帧item.tick(date)后调用chart.draw(),随后this._notify(chart, anims, date, 'progress')——这正是"onProgress在每一帧绘制后被调用"的出处;当items清空时触发complete

if (!items.length) { anims.running = false; this._notify(chart, anims, date, 'complete'); anims.initial = false; }

此外,Animator.startanims.duration取所有动画项时长的最大值(items.reduce((acc, cur) => Math.max(acc, cur._duration), 0)),因此多个属性并行动画时,进度条总步数等于最长动画的时长。

Controller 注册回调监听

src/core/core.controller.js 在图表构造时把onComplete/onProgress包装成对chart.options.animation的读取与调用:

function onAnimationsComplete(context) { const chart = context.chart; const animationOptions = chart.options.animation; chart.notifyPlugins('afterRender'); callCallback(animationOptions && animationOptions.onComplete, [context], chart); } function onAnimationProgress(context) { const chart = context.chart; const animationOptions = chart.options.animation; callCallback(animationOptions && animationOptions.onProgress, [context], chart); }

并在构造函数中通过animator.listen(this, 'complete', onAnimationsComplete)animator.listen(this, 'progress', onAnimationProgress)订阅事件(core.controller.js)。注意onComplete之前还会先执行chart.notifyPlugins('afterRender'),即"渲染完成后动画回调"。

测试用例的印证

test/specs/core.animator.tests.js 中'should fire onProgress for each draw'用 250ms 动画验证了上述行为:

const progress = (animation) => { count++; expect(animation.numSteps).toEqual(250); expect(animation.currentStep <= 250).toBeTrue(); };

即:numSteps严格等于配置的duration(250ms),currentStep不超过numStepsonProgress每帧触发且次数与afterDraw插件调用次数一致,最后以onComplete收尾。这条测试可作为"回调语义"的权威行为契约。

从示例到实战:脱离 Utils 的独立可运行实现

把官方示例落地到自己的页面时,需要:把<progress>元素、图表初始化、回调三者组合起来,并用字面量数据替代Utils。以下是一个可直接运行的完整版本:

<!DOCTYPE html> <html> <body> <label>Initial animation</label> <progress id="initialProgress" max="1" value="0" style="width: 100%"></progress> <label>Other animations</label> <progress id="animationProgress" max="1" value="0" style="width: 100%"></progress> <canvas id="chart" height="200"></canvas> <script type="module"> import Chart from 'chart.js/auto'; const initProgress = document.getElementById('initialProgress'); const progress = document.getElementById('animationProgress'); const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'], datasets: [ { label: 'Dataset 1', data: [65, -20, 80, 10, 56, -55, 40], borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.5)', }, { label: 'Dataset 2', data: [30, 50, -40, 81, -26, 55, 70], borderColor: 'rgb(54, 162, 235)', backgroundColor: 'rgba(54, 162, 235, 0.5)', } ] }; const chart = new Chart(document.getElementById('chart'), { type: 'line', data: data, options: { animation: { duration: 2000, onProgress(context) { const value = context.currentStep / context.numSteps; if (context.initial) { initProgress.value = value; } else { progress.value = value; } }, onComplete(context) { if (context.initial) { console.log('Initial animation finished'); } else { console.log('animation finished'); } } }, interaction: {mode: 'nearest', axis: 'x', intersect: false}, plugins: { title: {display: true, text: 'Chart.js Line Chart - Animation Progress Bar'} } } }); // 模拟"后续动画":随机更新数据并触发 chart.update() document.getElementById('randomize').addEventListener('click', () => { chart.data.datasets.forEach(ds => { ds.data = ds.data.map(() => Math.round(Math.random() * 200 - 100)); }); chart.update(); }); </script> <button id="randomize">Randomize</button> </body> </html>

运行后即可看到:页面加载时initialProgress随 2000ms 初始动画从 0 增长到 1;点击 Randomize 后chart.update()触发的动画则驱动animationProgress,两条进度条互不干扰。若希望把日志改为进度条"满格"的收尾动作,把onComplete中的console.log替换为任何业务回调即可。

动画配置扩展:duration、easing、transitions 与禁用动画

基于 docs/configuration/animations.md 可以进一步定制动画行为:

  • animation.duration:动画时长(毫秒),默认1000;本示例设置为2000便于观察进度条变化。
  • animation.easing:缓动函数,默认'easeOutQuart',可选'linear''easeInQuad''easeInOutExpo''easeOutBounce'等 30 余种(详见动画文档的 Easing 一节)。
  • animation.delay:动画开始前的延迟(毫秒),默认undefined
  • animation.loop:设为true时动画无限循环,进度条会反复走满。
  • transitions:按更新模式覆盖动画,核心模式有'active''hide''reset''resize''show'。例如默认resize模式duration: 0(缩放不带动画)、active模式duration: 400(悬停动画更短),这些默认值定义在 src/core/core.animations.defaults.js 的defaults.set('transitions', ...)中。
  • 禁用动画:将chart.options.animation = false可关闭全部动画;单独关闭某属性动画用chart.options.animations.colors = falsechart.options.animations.x = false;按模式关闭则把对应duration设为0。注意onProgress/onComplete/fn三个键被标记为不可 scriptable(见 core.animations.defaults.js),即回调本身不能写成按上下文动态解析的函数。

另外,动画的默认属性集合numbers['x', 'y', 'borderWidth', 'radius', 'tension'])与colors['color', 'borderColor', 'backgroundColor'])同样定义在 core.animations.defaults.js 中,多数数据集控制器会覆盖这些默认值。

相关文档导航

  • 动画配置与回调完整参考:docs/configuration/animations.md,其中 Animation Callbacks 一节即本示例的 API 依据;
  • 折线图数据集属性:docs/charts/line.md;
  • 数据标签结构与labels用法:docs/general/data-structures.md;
  • 通用选项与 Scriptable 选项机制:docs/general/options.md(Scriptable Options);
  • 示例数据辅助函数(非库 API):docs/scripts/utils.js;
  • 动画引擎实现:src/core/core.animator.js、回调注册 src/core/core.controller.js、默认值 src/core/core.animations.defaults.js;
  • 行为验证测试:test/specs/core.animator.tests.js。

【免费下载链接】Chart.jsSimple HTML5 Charts using thetag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询