Master Canvas Donut Chart Animations with JavaScript – A Step‑by‑Step Guide
This article combines a motivational start‑of‑school message with a comprehensive tutorial on building animated donut‑style charts using HTML5 canvas, covering data preparation, ring and ellipse drawing, legend creation, and smooth animation via requestAnimationFrame.
Introduction
The article begins with a brief encouragement for students to set new goals for the semester and then shifts to a technical tutorial on creating animated donut‑style charts with HTML5 canvas.
Data Construction
const donutData = [{
per: 0.45,
text: '学习课',
startColor: '#FFEA33', // yellow
stopColor: '#d8b616',
ellipseColor: '#FFD333'
}, {
per: 0.25,
text: '复习课',
startColor: '#7bc31f', // green
stopColor: '#96ec26',
ellipseColor: '#8FD43D'
}, {
per: 0.30,
text: '拓展课',
startColor: '#f0870c', // orange
stopColor: '#ff9413',
ellipseColor: '#FF8221'
}];Drawing the Ring
drawRing(startDeg, endDeg, strokeStyle, ellipseColor) {
const { ctx } = this;
ctx.save();
ctx.strokeStyle = strokeStyle;
ctx.beginPath();
ctx.lineWidth = R2 - R1;
ctx.arc(ctx.width / 2, ctx.height / 2, (R1 + R2) / 2, arcDeg(startDeg), arcDeg(endDeg));
ctx.stroke();
ctx.restore();
// this.drawEllipse(startDeg, ellipseColor);
// this.drawEllipse(endDeg, ellipseColor);
}Drawing the Ellipse
drawEllipse(rotate, color) {
const { ctx } = this;
rotate = deg(rotate);
const x = 0;
const y = -(R1 + R2) / 2;
ctx.save();
ctx.translate(ctx.width / 2, ctx.height / 2);
ctx.rotate(rotate);
ctx.moveTo(x, y);
ctx.beginPath();
ctx.fillStyle = color;
ctx.ellipse(x, y, EllipseR2, EllipseR1, 0, 0, 2 * Math.PI);
ctx.fill();
ctx.restore();
}Legend Implementation
The legend consists of a small dot, a line, an icon with a linear gradient, and a text label. A Legend class encapsulates all drawing logic.
class Legend {
constructor({ ctx, x, y, textMaxWidth, endX, startColor, stopColor, text }) {
this.ctx = ctx;
this.x = x;
this.y = y;
this.endX = endX;
this.textMaxWidth = textMaxWidth;
this.text = text;
this.dot = { r: 2.5, opacity: 0.8 };
this.icon = { w: 12, h: 12, r: 5, startColor, stopColor };
}
static MARGIN_BOTTOM = 4;
static LINE_HEIGHT = 14;
// draw methods omitted for brevity
}Animating with requestAnimationFrame
A small helper RafRunner wraps requestAnimationFrame to drive frame‑by‑frame animation.
class RafRunner {
constructor(requestAnimationFrame = window.requestAnimationFrame.bind(window)) {
this.requestAnimationFrame = requestAnimationFrame;
this.timingFunction = x => x;
}
handler(fn) { this._handler = fn; }
start(from, to, duration, timingFunction = x => x) {
const startTime = performance.now();
const animate = (now) => {
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
const value = from + (to - from) * this.timingFunction(progress);
this._handler(value, from);
if (progress < 1) this.requestAnimationFrame(animate);
};
this.requestAnimationFrame(animate);
}
} draw() {
const { source } = this;
if (!source.length) return;
const raf = new RafRunner();
let pos = 0;
raf.handler((recPer) => {
let part = source[pos];
const { startPer, per, lgr, ellipseColor } = part;
if (recPer >= startPer + per) {
const startDeg = ANGLE_360 * startPer;
const endDeg = ANGLE_360 * (startPer + per);
this.drawRing(startDeg, endDeg, lgr, ellipseColor);
this.drawPartLegend(part);
pos++;
part = source[pos];
if (!part) { this.drawEllipse(0, source[0].ellipseColor); return; }
}
const startDeg = ANGLE_360 * part.startPer;
const endDeg = ANGLE_360 * recPer;
this.drawRing(startDeg, endDeg, part.lgr, part.ellipseColor);
this.drawEllipse(0, source[0].ellipseColor);
});
raf.start(0, 1, 800, easeInOut);
}Final Thoughts
The tutorial also discusses handling text overflow, color distribution, legend overlap, and other practical considerations. The complete source code is available at https://github.com/chym123/donut-graph-demo.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
