插值:中间值怎么求
动画看上去是元素在动,落到实处是一个反复求值的问题:给定起点 from、终点 to 与进度 ,中间值是多少。插值(interpolation)就是这个问题的答案。它位于整套引擎的最底层——上面的缓动、弹簧、关键帧都只负责把 算出来,真正把 变成一个可以写进样式的值,是插值这一步。
麻烦在于「中间」并非只有一种含义。数字有唯一的线性中点;10px 这类带单位的字符串要先拆出数字部分;而颜色的中点取决于在哪个空间里求平均。@vega/anim 的 getMixer(from, to) 按值的形态分派到对应的混合器,调用方不必自己判断类型。
1 · 三类值的中间值
数字的插值是 from + (to − from) · p,一条直线。带单位的字符串按同样的规则处理数字部分,再把单位原样贴回去,于是 10px 到 200px 在 p = 0.5 处得到 105px。颜色是三类里唯一不能照搬这条规则的:sRGB 的通道值经过 gamma
编码,直接取平均得到的并不是「一半的光」。
2 · 颜色通道的 gamma 编码
sRGB 的 0–255 通道值不是光强的线性刻度:数值 128 对应的实际光强约为最大值的 21%,而非 50%。直接对编码值取平均,等于在一把非线性的尺子上求中点,结果落在偏暗的一侧。跨色相的过渡最容易暴露这一点——红到绿、橙到蓝,朴素平均的中段会塌成一片灰。
正确的做法是先把通道值解码回线性光(linear-light)、在光强上取平均、再编码回 sRGB。引擎的 mixColor 用平方近似这套换算:中点取
,比完整的 gamma 2.4 解码便宜得多,而在动画这种逐帧场景下肉眼分辨不出差别。
警示 · 平方近似只在 sRGB 内部成立,换到别的色彩空间要另算。真正按感知均匀混合、或需要沿色相环走一条指定方向的弧,得换一整套换算,见 色彩空间。
3 · 引擎的分派入口
getMixer 在建立动画时调用一次,返回一个只吃
的闭包,逐帧路径上不再有类型判断。这也是为什么关键帧数组里可以混放数字、字符串与颜色:每一段各自持有自己的混合器。
import { mixNumber, getMixer, mixColor } from '@vega/anim';
mixNumber(0, 100, 0.5); // 50 —— 线性
getMixer('10px', '200px')(0.5); // '105px' —— 拆数字, 回贴单位
mixColor('#f76707', '#1864ab', 0.5); // 线性光混色, 中点不发灰
插值只回答「中间值是多少」,不回答「什么时候取哪个
」。把均匀流逝的时间重新塑形成有快慢的
,是缓动的职责;让一个物理系统自己演化出
,是弹簧的做法;一串途经值之间的分段插值见关键帧。引擎里的 interpolate(input, output) 还能把任意标量区间映射到任意值域,是滚动与拖拽驱动的基础。