动画引擎原理 总览待审核
determinate · indeterminate · trickle · segmented · circular

进度条:把跳变的真实进度译成可信的运动

进度条看似只是「一条会变长的方块」,真正的难点全在动画这一侧:真实进度往往是从外部 离散跳变着到的 (下载分块 0 → 23 → 23 → 71 → 100),直接把宽度设过去会一段段跳变; 有时压根测不到真实进度 (跨页跳转、等待接口),只能给出「正在进行」的体感;有时它是 多段的 (stories),需要可暂停、可跳段的播放控制。把这些都收敛成一句话:进度条是把一个 不连续、甚至不可知的外部信号,翻译成视觉上连续而可信的运动。

本页用 @vega/anim 把五种形态走通,全程不依赖 CSS transition / animation:确定态用弹簧值源做平滑追赶、不确定态用 repeat:Infinity 循环并在拿到总量后落定 (与转盘落点同构)、 trickle 用渐近曲线诚实地骗、分段用一条时间轴游标支撑暂停 / 跳段、 圆环则把同一个 0→1 同时喂给 conic-gradient 与 SVG stroke-dashoffset

确定态:让显示值平滑追赶真实进度

最常见的形态:真实进度已知,但它是分块跳着到的。把宽度直接设成真实值,每块到达都会「啪」地 跳一下;正确做法是真实值只当目标,显示值由一个弹簧持续追它。每块到达时 animateTo(目标)当前显示值 + 当前速度为新初始条件重建弹簧——速度连续,不会从静止 重启而顿挫。下图蓝线是真实进度 (阶梯),橙线是显示进度 (被弹簧抹平的追赶轨迹)。

真实进度
0%
显示进度
0%
真实 0% 显示 0% 追赶速度 0%/s
阻尼调低 (如 12),每块到达后显示值会过冲再回落,带一点弹性手感;调高 (如 44) 则 沉稳无过冲。无论参数怎么变,连点手动来一块都不会看到「跳变」——因为新目标是从当前 value / velocity 续上的,这正是 animateTo 相对「cancel 旧动画再 起一条」的根本优势 (见飞行中重定向)。
@vega/anim — 真实值当目标,显示值用弹簧续速追赶

  

不确定态:先空转,拿到总量再落定

当总量未知 (服务端还在统计、流式响应没有 Content-Length),进度条无法给出百分比,只能表达 「正在进行」:一段repeat:Infinity 的循环——滑块来回扫,或条纹斜向流动。一旦拿到 总量,就交接成确定态:停掉循环,从 0 按 easeOut 填到 100%。这与转盘落点 的「不定 → 定」是同一个结构——空转是占位,落定才是结果。

不确定 · 循环占位 落定 · 填到 100% 完成
空闲

测不到真实进度:trickle 的「诚实假进度」

单页应用路由切换、首屏白屏期——这些场景没有可读的进度,但用户需要反馈。业界做法 (NProgress 一类) 是给一条假进度:朝一条渐近线 (如 90%) 减速逼近、永不到顶,真正完成时再一把 snap 到 100%。关键在那条永不触及的上限——它诚实地承认「我不知道还剩多少」,只保证 「还在动」。这里用一段 expoOut 把 0 推向上限:前期快、越接近越慢,天然的渐近形状。

假进度
0%
上限这条红线是 trickle 的灵魂:它故意到不了。若让假进度跑到 100% 却迟迟不结束,反而比卡在 90% 更伤信任。把上限拉到 95% 再开始,会看到爬升明显更慢——离上限越近,expoOut 的速度 越趋于零。

分段进度:stories 与时间轴游标

Instagram / WhatsApp 的故事条是 N 段独立进度的串联:每段定时填满后自动推进到下一段。它本质是一条 时间轴:整体进度 p ∈ [0, N),第 ⌊p⌋ 段正在填,段内 进度 p − ⌊p⌋。于是暂停 / 继续就是播放器的 pause / play, 跳段就是把游标 seek 到段边界——播放控制天然支撑这些交互。

第 1 段
1/5 状态 就绪
下一段 / 上一段seek 到段边界、不打断播放;播放 / 暂停切的是同一个 游标的运行状态。因为「时间 → 各段填充」是纯函数,跳到哪一段都能立刻画对,这就是把 stories 建在 播放器之上的好处。

圆环进度:同一个 0→1,两种画法

把进度画成圆环有两条互不相干的实现路径,却吃同一个 0→1:其一 conic-gradient——按角度 扫出扇形,叠一个内圆挖空成环;其二 SVG stroke-dashoffset——把虚线段长设成 周长,dashoffset = 周长 × (1 − p) 推出实线弧。拖滑块看两者严丝合缝地同步;开不确定态 则是一段固定弧 + repeat:Infinity 的旋转,即最常见的 spinner。

0%
conic-gradient
stroke-dashoffset
同一个 0→1 → conic 角度 / dashoffset 弧长

  
五种形态拆开看是五个交互,合起来是一条主线:进度条要解决的从来不是「画一个矩形」,而是 把外部信号在时间上重新塑形——能测就平滑追赶、不能测就诚实渐近、多段就交给游标。值怎么算、 时间怎么塑形、播放怎么控制,正是本系列从 插值播放控制 的同一套地基落到 UI 上。