Chart.js — Canvas 渲染入门级图表
是什么
Chart.js 是一个让你用三行配置画出图表的 JavaScript 库。日常类比:像 Excel 的”插入图表”按钮——你给它一份表格、选一个图类型,它直接把图画出来,连配色、坐标轴、Tooltip 都默认调好。
你写:
new Chart(ctx, { type: 'line', data: { labels: ['一月','二月','三月'], datasets: [{ data: [10, 20, 15] }] }, options: {}})3 行,一条折线就上线。它默认在 HTML5 Canvas 上画图(不是 SVG),所以几千点也不卡 DOM。
为什么重要
不理解 Chart.js 的设计,下面这些事都没法解释:
- 为什么前端教学第一周的”画个图”作业基本都用它(极简 API + 文档友好)
- 为什么后台管理项目宁可不要花哨效果也选它,而不是 D3——D3 学一周才能出第一张图
- 为什么 Canvas 上画图比 SVG 省内存——SVG 每个点都是 DOM 节点,Canvas 是一张位图
- 为什么数据量超过 1 万点要装 decimation 插件——Canvas 每帧都要重画整张图
核心要点
Chart.js 的设计可以拆成 四层对象:
-
Chart 实例:每张图一个
new Chart(ctx, config),持有那块 canvas 的画笔。类比:一张画布 + 一个画家。 -
Controller:每种图类型一个(line / bar / pie …),决定”怎么把数据翻成像素位置”。类比:菜谱——同样的食材,按”折线菜谱”画就是折线,按”柱状菜谱”画就是柱图。
-
Element + Scale:Element 是真正画的零件(一根线、一根柱、一个点);Scale 是坐标轴算法(线性 / 时间 / 类别)。类比:尺子(Scale)+ 笔触(Element)。
-
Plugin:生命周期钩子(
beforeDraw/afterDraw/beforeUpdate等),让你在画图过程的任意一帧插入自己的代码。类比:流水线上的工位,你可以在任意工位加自己的动作。
四层加起来叫 可扩展的成品库——默认就能用,要改也改得动。
实践案例
案例 1:5 分钟最小起手式
<canvas id="c"></canvas><script type="module"> import Chart from 'https://cdn.jsdelivr.net/npm/chart.js/auto/+esm' new Chart(document.getElementById('c'), { type: 'bar', data: { labels: ['周一','周二','周三','周四','周五'], datasets: [{ label: '咖啡杯数', data: [3, 5, 2, 6, 4] }] } })</script>不需要打包工具、不需要 React,浏览器里就能跑。/auto 后缀让它自动注册所有 controller,最省事的入门姿势。
案例 2:响应式 + 自定义 Tooltip
new Chart(ctx, { type: 'line', data: { /* ... */ }, options: { responsive: true, maintainAspectRatio: false, plugins: { tooltip: { callbacks: { label: (item) => `销量 ${item.parsed.y} 件` } } } }})responsive: true 让图随容器 resize 重画;tooltip.callbacks 改一行就能定制悬浮提示文字。这两个是 90% 项目的第一改动。
案例 3:插件钩子画水印
const watermark = { id: 'watermark', afterDraw: (chart) => { const { ctx, chartArea } = chart ctx.save() ctx.fillStyle = 'rgba(0,0,0,0.05)' ctx.font = '40px sans-serif' ctx.fillText('DRAFT', chartArea.left + 20, chartArea.top + 50) ctx.restore() }}new Chart(ctx, { type: 'line', data, plugins: [watermark] })afterDraw 在每次重画之后执行,你拿到原生 canvas context,想画啥画啥。这是 Chart.js 真正的扩展点。
踩过的坑
-
Canvas 没有 DOM:不能用 CSS 改图表元素的样式,所有外观(颜色、字号、间距)都得走
options。新人第一次想 inspect 元素改 hover 颜色就懵了。 -
高 DPR 屏幕糊:Mac Retina 上默认渲染分辨率不够,文字边缘有锯齿。解法:
options.devicePixelRatio = window.devicePixelRatio或让父容器有明确宽高。 -
v2 / v3 / v4 多次 breaking:旧教程里
Chart.defaults.global.xxx在 v3+ 已经移除;scale 配置从xAxes: [{...}]数组变成x: {...}对象。看教程先确认版本。 -
大数据量卡顿:超过 1 万点每帧都要重画,CPU 占满。解法:装
chartjs-plugin-decimation自动采样,或换成 WebGL 系(deck.gl / regl)。 -
无障碍弱:屏幕阅读器读不到 canvas 内容。政府站、教育站要么提供
<table>fallback,要么换 SVG 系(Recharts / Observable Plot)。
适用 vs 不适用场景
适用:
- 后台管理面板的常规折线 / 柱图 / 饼图(占 80% 业务图表需求)
- 教学场景:让零基础学生第一次出图的最短路径
- 需要 Canvas 性能(千点级散点图)但又不想啃 D3 的项目
- framework-agnostic:原生 JS / Vue / Svelte / Astro 都直接能用,不绑死框架
不适用:
- 需要复杂交互(地理地图、桑基图、关系图、3D) → 用 ECharts / D3
- 数据量极大(10 万 +)→ 走 WebGL(deck.gl / regl)
- 需要每根柱子独立动画 + 形变的极端定制视觉 → D3 + 手画 SVG
- 强无障碍要求 → 改用 SVG 系(Recharts / Observable Plot)
历史小故事(可跳过)
- 2013 年:Nick Downie 一个人写了第一版 Chart.js,开源到 GitHub。当时 D3 已经火了,但 D3 太硬核——Nick 想做”傻瓜版”,让人三行出图。
- 2017 年:v2 改写为可扩展架构,引入 Controller 概念——第一次让用户能加自定义图类型。
- 2020 年:v3 重构为 ESM + tree-shaking,bundle 可以压到 30KB;代价是要手动 register 用到的 controller / scale。
- 2023 年:v4 用 TypeScript 重写,类型完全暴露,性能优化又上一档。
13 年过去,它仍是 npm 上下载量最高的图表库之一——证明”够用就好”在工程界永远有市场。
学到什么
- API 极简是产品力——三个字段
type / data / options击败了无数功能更全的库 - Canvas vs SVG 不是优劣是权衡——DOM 数 / 交互精度 / 内存 / 无障碍各有取舍
- 插件钩子机制让”成品库”也能”定制”,避开了”功能多但僵硬”的死局
- 教学库 ≠ 玩具:基础够扎实、够稳定,反而成了真实业务的常驻选项
延伸阅读
- 官方文档:Chart.js Docs(中文翻译有但更新慢,建议读英文)
- 例子大全:Chart.js Samples(每种图都有 live demo + 源码)
- d3 —— Chart.js 仰望的”硬核祖师”,理解 D3 才知道 Chart.js 替你省了什么
- recharts —— React 项目首选 SVG 系,对照看 declarative API 和 Canvas 的差异
- echarts —— 配置项更全的中国系图表库,体量更大但学习曲线也更陡
- plotly-js —— 自带缩放框选交互,科学计算和数据探索更顺手
- observable-plot —— 新一代 grammar of graphics 风格,思路完全不同
关联
- d3 —— 底层数据驱动祖师,Chart.js 的设计哲学在它对面
- recharts —— 同类教学定位,但走 React + SVG 路线
- echarts —— 同样 Canvas 渲染,但配置项体量大十倍
- observable-plot —— grammar of graphics 思路,对比”配置式”和”组合式”