基于HTML5 Canvas实现类MUI CircularProgress Spinner的优化问询
精准实现Canvas版MUI风格CircularProgress加载动画
你现在用实验出来的quadroStep虽然能跑,但这种“试出来的数值”确实不严谨,也很难精准对齐MUI的原生动画。要解决这个问题,核心是用数学缓动曲线替代实验值,同时把动画从“固定步数驱动”改成“时间驱动”,这样逻辑清晰还能完美匹配MUI的效果。
先搞懂MUI加载圈的动画逻辑
MUI的CircularProgress其实是两个对称的阶段循环:
- 扩张阶段:圆弧从一个小间隙开始,先加速变长,接近满圆时减速停下
- 收缩阶段:圆弧从接近满圆的状态,先加速缩短,回到初始小间隙时减速停下
整个过程的加减速,用二次缓动曲线(ease-in-out)就能精准控制,根本不需要试数值。
重构后的代码(精准无魔法数值)
用requestAnimationFrame替代setInterval(动画更流畅),所有参数都可配置,完全对齐MUI:
window.addEventListener("load", () => { const canvas = document.querySelector("canvas"); const ctx = canvas.getContext("2d"); // 所有参数都可以直接调整,对应MUI的样式/动画 const opts = { centerX: canvas.width / 2, centerY: canvas.height / 2, radius: 30, strokeWidth: 6, strokeColor: "rgba(0,0,0,0.3)", // 加载圈的固定间隙(MUI默认大概是1/8圆) fixedGap: Math.PI / 8, // 一个完整循环的时长(MUI默认约1.4秒) cycleTime: 1400 }; let startTime = null; // 二次缓动函数:ease-in(加速) const easeIn = t => t * t; // 二次缓动函数:ease-out(减速) const easeOut = t => 1 - (1 - t) ** 2; // ease-in-out:先加速后减速,完美匹配MUI的加减速节奏 const easeInOut = t => t < 0.5 ? easeIn(t * 2) / 2 : 1 - easeIn((1 - t) * 2) / 2; const draw = timestamp => { if (!startTime) startTime = timestamp; // 计算当前在循环中的进度(0到1) const cycleProgress = ((timestamp - startTime) % opts.cycleTime) / opts.cycleTime; ctx.clearRect(0, 0, canvas.width, canvas.height); let arcLength; if (cycleProgress < 0.5) { // 前半段:圆弧扩张,从固定间隙到接近满圆 const phase = cycleProgress * 2; // 用缓动曲线控制弧长变化,从初始间隙到(2π - 固定间隙) arcLength = opts.fixedGap + (Math.PI * 2 - 2 * opts.fixedGap) * easeInOut(phase); } else { // 后半段:圆弧收缩,从接近满圆回到固定间隙 const phase = (cycleProgress - 0.5) * 2; arcLength = (Math.PI * 2 - opts.fixedGap) - (Math.PI * 2 - 2 * opts.fixedGap) * easeInOut(phase); } // 让加载圈持续旋转(线性速度,和MUI一致) const startAngle = (timestamp - startTime) / opts.cycleTime * Math.PI * 2; const endAngle = startAngle + arcLength; // 绘制圆弧 ctx.beginPath(); ctx.arc(opts.centerX, opts.centerY, opts.radius, startAngle, endAngle); ctx.lineWidth = opts.strokeWidth; ctx.strokeStyle = opts.strokeColor; ctx.stroke(); requestAnimationFrame(draw); }; requestAnimationFrame(draw); });
HTML部分给canvas加个宽高(避免默认尺寸过小):
<html> <head></head> <body> <canvas width="100" height="100"></canvas> </body> </html>
为什么这个方案更靠谱
- 无魔法数值:所有计算都基于数学缓动曲线,
easeInOut直接实现加减速,再也不用试/75这类实验性数值 - 时间驱动动画:用
requestAnimationFrame和时间戳计算进度,不管设备帧率如何,动画节奏都一致 - 完全对齐MUI:参数可以直接调整,比如改
cycleTime就能匹配MUI的循环速度,改fixedGap就能调整间隙大小 - 逻辑清晰:旋转和弧长变化分离,旋转是匀速的,弧长变化由缓动控制,和MUI的原生动画逻辑完全一致
内容的提问来源于stack exchange,提问作者holem
相关产品推荐
相关产品推荐

