SVG 车轮运动可信度:辐条混叠与地面线速度匹配 (Making SVG Wheel Motion Read Correct - Spoke Aliasing and Ground-Speed Matching)
几何完全正确的车轮动画,仍可能"看起来不对"。原因是动起来的图形要经过人眼和时间两个环节,而这两个环节都不在几何模型的假设里。本文记录两类反直觉的失效,以及无头逐帧校验的可靠做法。
一、等分辐条必然混叠,旋转速度是假的
现象
12 根均匀分布的辐条,按 2s/圈 匀速旋转。渲染结果逐帧都正确,但看上去像在以 12 倍速狂转,或者贴近帧率时出现频闪、甚至倒转(马车轮效应)。
根因
n 根等角间隔的辐条构成的图形,在旋转 360°/n 后与自身完全重合。也就是说这个图形的真实周期不是 360°,而是 360°/n。写 360° 只是让它转回了同一个样子——视觉上等价于转了一整圈 n 次。
这不是采样定理的问题,是图形本身缺少区分度:没有任何特征能告诉眼睛"转了多少"。加密采样、提高帧率都无效。
解法
两件事一起做:
- 打破等分:让辐条角度不均匀(本次用了 0/30/75/110/150/195/240/285/320,间距刻意带抖动)。一旦不等分,图形的真实周期就回到 360°,视觉转速与实际转速一致。
- 加一个不对称标记:轮缘上的气门嘴 / 反光片。眼睛靠它跟踪真实角位移,这是最直接的运动线索。
判据
只要车轮上"看不出转了多少",就先怀疑存在旋转对称性,而不是怀疑帧率或缓动函数。
二、地面纹理必须匹配车轮线速度,否则会"打滑"
现象
车轮转得挺好看,路面虚线也在滚动,但整体感觉车轮在冰上打滑——因为两者速度不一致。
物理关系
车轮角周期 T、外半径 r 时,地面线速度
v = 2πr / T地面任何纹理(路面虚线、路缘标记、枕木)的滚动周期 p 必须满足动画时长 = p / v。
本次实测:轮胎外半径 r ≈ 94(描边圆的外沿,不是描边中心线),T = 1.2s → v ≈ 492 px/s;虚线周期 p = 60 + 50 = 110 → 时长 110 / 492 ≈ 0.224s,取 0.23s。
易错点
- 半径要取轮胎外沿,不是
r属性值(描边有宽度,stroke-width/2要加上)。 - 传动比也要自洽:曲柄周期
T_crank与车轮周期T_wheel之比就是传动比,本次 2 / 1.2 ≈ 1.67,是个像样的齿比。随便取两个互不相干的周期会露馅。 - 链条等细节纹理不必严格匹配——按真实速度流动时周期只有几十毫秒,会与帧率混叠成抖动。主动放慢到不频闪的速度,视觉上更干净。
三、无头逐帧校验:不要信 --virtual-time-budget
用 headless Chrome 截图校验动画是可行的:
chrome --headless --disable-gpu --hide-scrollbars \
--screenshot=out.png --window-size=900,600 \
--virtual-time-budget=900 file:///path/to/x.svg但 --virtual-time-budget 不会精确落在你预期的动画时刻上。 本次实测:--virtual-time-budget=3600 与 3780 对一帧 4.2s 循环的眨眼动画截出了完全相同的画面,据此险些得出"眨眼动画没生效"的错误结论——而动画本身从头到尾都是对的。
原因是虚拟时钟推进与 CSS 动画时间的对齐关系不可控。可靠做法是把动画直接钉到目标进度:用负 animation-delay 覆盖,配合极小的 budget。
<!-- 目标:4.2s 循环的第 90%(该帧眼睛闭合)→ 延迟 -3.78s -->
<style>.eyes{ animation-delay:-3.775s !important; }</style>再配一个静态对照:把关键帧里的 transform 直接写成静态值,验证数学本身是否正确。
| 实验 | 目的 | 做法 |
|---|---|---|
| 静态对照 | 变换数学对不对 | animation:none + 手写目标 transform |
| 负延迟 | 关键帧会不会被触发 | animation-delay:-<目标秒>s + 极小 budget |
两者都通过,动画就是好的;此时若整帧截图仍看不出变化,问题在测量工具,不在动画。
相关
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Frontend