You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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>

为什么这个方案更靠谱

  1. 无魔法数值:所有计算都基于数学缓动曲线,easeInOut直接实现加减速,再也不用试/75这类实验性数值
  2. 时间驱动动画:用requestAnimationFrame和时间戳计算进度,不管设备帧率如何,动画节奏都一致
  3. 完全对齐MUI:参数可以直接调整,比如改cycleTime就能匹配MUI的循环速度,改fixedGap就能调整间隙大小
  4. 逻辑清晰:旋转和弧长变化分离,旋转是匀速的,弧长变化由缓动控制,和MUI的原生动画逻辑完全一致

内容的提问来源于stack exchange,提问作者holem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 16:50:34