用canvas手动画饼图最可控,需手动计算弧度、起始/终止角并调用beginpath()/fill()等api;svg适合交互与响应式,需正确使用path的arc指令;chart.js需注意图例隐藏、cutout设置及移动端触控适配。

用 canvas 手动画饼图最可控,但得自己算角度和坐标
浏览器原生不支持 HTML 饼图标签,<canvas></canvas> 是最轻量、兼容性最好、也最贴近“纯 HTML/CSS/JS 实现”的方案。关键不是画圆,而是把数据转成弧度、处理起始/终止角、还要手动填充扇形——容易漏掉 ctx.beginPath() 或忘记 ctx.closePath(),导致扇形连不上边、颜色溢出。
实操建议:
- 先对数据求和,每个值除以总和再乘
2 * Math.PI得弧度 - 用
ctx.arc(x, y, radius, startAngle, endAngle)画弧,注意起始角从-Math.PI / 2(12 点钟方向)开始才符合直觉 - 每段扇形必须单独调用
ctx.fill(),否则所有扇形共用最后设置的 fillStyle - 如果需要描边或高亮选中态,记得在
fill()前加ctx.stroke()并控制lineWidth
const ctx = canvas.getContext('2d');
const data = [30, 45, 25];
const total = data.reduce((a, b) => a + b, 0);
let startAngle = -Math.PI / 2;
<p>data.forEach((val, i) => {
const sliceAngle = (val / total) <em> Math.PI </em> 2;
ctx.beginPath();
ctx.moveTo(100, 100); // 圆心
ctx.arc(100, 100, 80, startAngle, startAngle + sliceAngle);
ctx.closePath();
ctx.fillStyle = ['#ff6b6b', '#4ecdc4', '#45b7d1'][i];
ctx.fill();
startAngle += sliceAngle;
});</p>
SVG 饼图适合需要交互和响应式,但路径计算不能偷懒
比起 canvas,SVG 天然支持事件绑定(如 onclick)、CSS 样式、缩放不失真,但没有内置饼图元素,仍需用 <path></path> 手写 d 属性。常见错误是直接套用极坐标公式却忽略 SVG 的坐标系原点在左上角、且弧线指令 A 的参数顺序易错。
实操建议:
- 用
arc指令生成扇形路径时,必须指定大弧标志(1或0)和方向(1表示顺时针),否则小数据段可能反向画成优弧 - 推荐封装一个
getPiePath(centerX, centerY, radius, startAngle, endAngle)函数,返回d字符串,避免每次手算端点坐标 - 若要悬停高亮,直接给
<path></path>加class,用 CSS 控制stroke和stroke-width更稳定 - 响应式场景下,用
viewBox替代固定width/height,避免缩放变形
别硬扛——用 Chart.js 时 options.plugins.legend.display = false 不等于隐藏图例
很多开发者以为关掉图例就只剩饼图,结果发现空白区域还在、点击无反应、甚至数据标签还堆在边上。这是因为 Chart.js 的饼图默认启用 legend 插件,但禁用后,tooltip 和 onClick 事件依然依赖图例逻辑链;更隐蔽的问题是,plugins.legend.display: false 只隐藏 DOM 元素,不释放空间,外层容器 padding 仍存在。
实操建议:
- 真正精简界面,要同时设
plugins.legend.display = false和layout.padding = 0 - 自定义 tooltip 显示内容?别改
tooltips.callbacks.label(v2)或tooltip.callbacks.label(v3+),而应重写tooltip.external避免与图例冲突 - 如果只想要静态图(无动画、无交互),初始化时传
options.animation = { animateRotate: false, animateScale: false },减少重绘开销 - v3.9+ 中,
cutout默认是'50%'(环形图),要做实心饼图必须显式设为0
移动端点击不准?touchstart 事件在饼图上要防误触
在手机上点饼图扇形,经常触发不了 click,或者点了 A 区域却响应了 B 数据——根本原因是 canvas 或 SVG 上没有元素级目标,事件只能靠坐标换算,而手指触控热区比鼠标大得多,且系统有 300ms 延迟和 click 合并行为。
实操建议:
- 优先监听
touchstart而非click,并在事件回调里立刻e.preventDefault()阻止默认行为 - 坐标计算前,用
getBoundingClientRect()获取画布真实位置,再减去event.touches[0].clientX/Y,别直接用offsetX/Y(在SVG中不可靠) - 添加最小触控半径判断:算出点击点到圆心距离,只在
radius * 0.8 范围内才继续扇形判定 - 如果用了
Chart.js,别依赖getElementAtEvent()返回数组第一个,它可能为空;应改用getElementsAtEventForMode(e, 'nearest', { intersect: true })
实际项目里,扇形点击判定和角度累加偏移是最常返工的部分,尤其是数据动态更新后没重置 startAngle,会导致下一帧所有扇形错位。这些细节不会报错,但图一动就崩。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











