进度环与仪表盘
进度环的文档是 一个数值 加上极少几何(圆、弧、刻度)。世界单位是 viewBox(常见 0 0 100 100 或按半径 0 0 32 32);没有漫游相机。能 CSS 解决就不要上 SVG;上了 SVG 也不要把 stroke-dasharray 当通用动画框架。
1. 场景怎么切
值:0..1 或业务单位,读屏用 aria-valuenow
几何:circle / path 弧(必须是节点,若要自定义描边与无障碍)
装饰:刻度、目标线、sparkline
数字:HTML 叠在圆心(IME 与字体走 DOM)或 <text>
必须是节点:环本身(若产品要主题色、焦点、标题)、可点的阈值标记。仪表盘上每个 KPI 一张小 SVG,不要把整屏仪表画进一棵 2000 节点的树。
可以光栅:背景装饰、不交互的 sparkline 历史、大屏墙的循环视频。实时值仍要用 DOM/ARIA 更新,不要烧进视频。
export function ringDash(
radius: number,
progress: number,
): { array: string; offset: string } {
const c = 2 * Math.PI * radius;
const p = Math.min(1, Math.max(0, progress));
return { array: `${c}`, offset: String((1 - p) * c) };
}
export function applyRing(el: SVGCircleElement, progress: number): void {
const r = el.r.baseVal.value;
const { array, offset } = ringDash(r, progress);
el.setAttribute("stroke-dasharray", array);
el.setAttribute("stroke-dashoffset", offset);
el.setAttribute("transform", `rotate(-90 ${el.cx.baseVal.value} ${el.cy.baseVal.value})`);
}
从 12 点开始要旋转 -90°。pathLength="1" 时 dash 可以用 0.75 1 这种归一化,少一次 2πr。WAAPI 改 stroke-dashoffset 即可;不要每帧改 d。
纯 CSS 替代:conic-gradient + mask 做环,数值仍在 HTML。无自定义刻度时优先 CSS,见 选型。描边动画不要上升成状态机,见 路径描边。
export function bindMeter(el: SVGElement, value: number, max: number): void {
el.setAttribute("role", "meter");
el.setAttribute("aria-valuemin", "0");
el.setAttribute("aria-valuemax", String(max));
el.setAttribute("aria-valuenow", String(value));
}
圆心百分比用 HTML overlay,避免 <text> 的基线在不同引擎上跳,见 Overlay。
2. 仪表盘
多个环、半圆仪表、子弹图:共享一个 viewBox 或每个 widget 独立 SVG。独立更容易虚拟化(滚出视口就卸载)。刻度线用 <use> 或一条 path,不要 100 个 line 各带滤镜。
阈值变色是数据 → class / stroke,不是再挂一套 SMIL。用户文件里的 animate 不要进仪表组件。
3. 不适用
- 把 dashoffset 动画拿去做流程图高亮、白板笔迹:长度一变公式全坏,且不好暂停到确定状态。
- 环上用滤镜发光还每秒更新:光栅成本高于圆环本身。
- 只画环不暴露数值:读屏听到「图」,听不到 73%。
4. 失败形态
| 症状 | 原因 |
|---|---|
| 进度从 3 点长出来 | 没旋转到 12 点 |
| 100% 对不齐 | 用了 CSS 半径而 r 是用户单位,或 stroke 居中吃掉直径 |
| 动画抖 | 每帧重设整个 d,或 layout 被旁边 DOM 文字撑动 viewBox |
| 读屏无值 | 没有 role="meter" / aria-valuenow |
| 缩成图标后环消失 | 没有 viewBox,当位图盒子压扁 |
权威资料
核对日期:2026-08-26