数值 · 02
三次贝塞尔缓动
Cubic Bézier easing · ease-cubic-bezier四个点定出一条时间曲线;拖动两个控制点,速度感就变了。
三次贝塞尔用横轴表示时间、纵轴表示进度。控制点的横坐标决定加速发生在什么时候,纵坐标还能让进度越过终点。只看曲线形状容易误判,配一块同速行进的方块,才能看到起步和落点的差别。
公式
B(t) = (1−t)³P0 + 3(1−t)²tP1
+ 3(1−t)t²P2 + t³P3
find t where Bx(t) = time; output By(t)参数
| 本页默认 | 成熟实现 | |
|---|---|---|
| P1, P2 | (.2, .8), (.2, 1) | Motion cubicBezier(x1,y1,x2,y2)x 保持在 0…1,才能按时间求出唯一进度。 |
| solver | Newton → bisection | Motion: bracketed Newton, cap at i = 40平坦处牛顿步可能越界,要退回二分。 |
怎样做好
- 把 x 当时间、y 当进度:同样的控制点调换 x/y,不是同一条缓动。motion/packages/motion-utils/src/easing/cubic-bezier.ts:20–29
- 靠近平坦区时保护数值求解;Motion 用带区间保护的牛顿迭代,越界就二分。motion/packages/motion-utils/src/easing/cubic-bezier.ts:39–61
- 别只记
ease-out的名字;把实际控制点放进动效规格,交接时才不会换曲线。www-motion/rewrite/portfolio-photographer/app.js:5
在我们的案例里
24 个案例涉及三次贝塞尔;portfolio-photographer 在悬停卡片复位时直接写了 cubic-bezier(.16,1,.3,1)。它服务的是松手后的柔和落点,而不是卡片正在跟随指针时的速度。
出现在 24 个案例
参考实现 · basics.js
/** CSS cubic-bezier(x1, y1, x2, y2). Newton for speed, bisection when the slope is flat. */
export function cubicBezier(x1, y1, x2, y2) {
const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
const X = t => ((ax * t + bx) * t + cx) * t;
const Y = t => ((ay * t + by) * t + cy) * t;
const dX = t => (3 * ax * t + 2 * bx) * t + cx;
function solve(x) {
let t = x;
for (let i = 0; i < 8; i++) {
const err = X(t) - x, slope = dX(t);
if (Math.abs(err) < 1e-7) return t;
if (Math.abs(slope) < 1e-6) break; // flat: Newton would shoot off
t -= err / slope;
if (t < 0 || t > 1) break; // left the curve: fall back
}
let lo = 0, hi = 1; t = x;
for (let i = 0; i < 50; i++) {
const v = X(t);
if (Math.abs(v - x) < 1e-7) break;
if (v < x) lo = t; else hi = t;
t = (lo + hi) / 2;
}
return t;
}
return x => (x <= 0 ? 0 : x >= 1 ? 1 : Y(solve(x)));
}