缓动曲线编辑器:拖控制点
cubic-bezier(x1,y1,x2,y2) 只有四个数。两个端点被固定在
与
——缓动必须从 0 开始、到 1 结束——形状全部交给两个控制点
与
。Bézier 曲线是控制点的加权混合,
与
像两块磁铁,把曲线从对角线(linear)拽向自己。
控制点的 被锁在 内,因为时间必须单调推进; 不设限,拉到 1 以上或 0 以下就得到越过端点的过冲与回弹。
1 · 控制点与曲线形状
手柄线(
与
)的斜率就是曲线在两端的瞬时速度。起点手柄越平,启动越慢,得到 ease-in;终点手柄越平,收尾越慢,得到 ease-out。CSS 的四个关键字 ease / ease-in / ease-out / ease-in-out 各自对应一组固定的控制点,其中 ease 的
是唯一让两个控制点共用同一个
的一组。
2 · 参数曲线与反解
cubic-bezier 不是 ,而是一条以参数 描述的曲线: 与 都是 的三次多项式。这带来一个容易被忽略的分工——画这条曲线与用这条曲线,走的不是同一条代码路径。
画曲线时对
均匀采样,逐点取
连成折线,不需要任何求解;上面那块画布正是这么描点的。而动画引擎手上只有时间进度
,必须先求出满足
的
,才能代入
。引擎用 Newton-Raphson 迭代求根,导数过小时退回二分——浏览器实现 cubic-bezier() 时做的是同一件事。
注 · 沿 均匀采样得到的点在 轴上并不均匀:、 的 挤在一侧时,曲线的采样点也随之疏密不均。描点画图看不出来(折线足够密),但若拿这批点当查找表反查 ,误差会集中在 分布最稀的那一段。这正是引擎不用查表而用迭代的原因。
import { Animation, cubicBezier } from '@vega/anim';
// 编辑器拖出来的两个控制点
const ease = cubicBezier(x1, y1, x2, y2);
const anim = new Animation({
keyframes: [0, 1],
duration: 1500,
ease, // 引擎给定时间 x, Newton 反解 u 再求 y
repeat: Infinity,
onUpdate: (value) => {
ball.style.transform = `translateX(${value * maxX}px)`;
},
});
具名缓动与三个朝向同源派生的原理见缓动。若要的不是一条预设曲线,而是有质量与初速度、时长由参数自己决定的物理回弹,换成弹簧。