跳到主要内容

进度环与仪表盘

进度环的文档是 一个数值 加上极少几何(圆、弧、刻度)。世界单位是 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,避免 &lt;text> 的基线在不同引擎上跳,见 Overlay

2. 仪表盘

多个环、半圆仪表、子弹图:共享一个 viewBox 或每个 widget 独立 SVG。独立更容易虚拟化(滚出视口就卸载)。刻度线用 &lt;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