几何完全正确的车轮动画,仍可能"看起来不对"。原因是动起来的图形要经过人眼和时间两个环节,而这两个环节都不在几何模型的假设里。本文记录两类反直觉的失效,以及无头逐帧校验的可靠做法。

一、等分辐条必然混叠,旋转速度是假的

现象

12 根均匀分布的辐条,按 2s/圈 匀速旋转。渲染结果逐帧都正确,但看上去像在以 12 倍速狂转,或者贴近帧率时出现频闪、甚至倒转(马车轮效应)。

根因

n 根等角间隔的辐条构成的图形,在旋转 360°/n 后与自身完全重合。也就是说这个图形的真实周期不是 360°,而是 360°/n。写 360° 只是让它转回了同一个样子——视觉上等价于转了一整圈 n 次。

这不是采样定理的问题,是图形本身缺少区分度:没有任何特征能告诉眼睛"转了多少"。加密采样、提高帧率都无效。

解法

两件事一起做:

  1. 打破等分:让辐条角度不均匀(本次用了 0/30/75/110/150/195/240/285/320,间距刻意带抖动)。一旦不等分,图形的真实周期就回到 360°,视觉转速与实际转速一致。
  2. 加一个不对称标记:轮缘上的气门嘴 / 反光片。眼睛靠它跟踪真实角位移,这是最直接的运动线索。

判据

只要车轮上"看不出转了多少",就先怀疑存在旋转对称性,而不是怀疑帧率或缓动函数。

二、地面纹理必须匹配车轮线速度,否则会"打滑"

现象

车轮转得挺好看,路面虚线也在滚动,但整体感觉车轮在冰上打滑——因为两者速度不一致。

物理关系

车轮角周期 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