贝塞尔计算公式:
一阶:
function bezier1(t: number, x0: number, x1: number) {
return x0 + (x1 - x0) * t
}
二阶:
function bezier2(t: number, x0: number, x1: number, x2: number) {
const rt = 1 - t
const p = Math.pow
return x0 * p(rt, 2) + 2 * x1 * rt * t + x2 * p(t, 2)
}
三阶:
function bezier3(t: number, x0: number, x1: number, x2: number, x3: number) {
const rt = 1 - t
const p = Math.pow
return x0 * p(rt, 3) + 3 * x1 * p(rt, 2) * t + 3 * x2 * rt * p(t, 2) + x3 * p(t, 3)
}
n阶:
function bezierN(t: number, xArr: number[]) {
const rt = 1 - t
const p = Math.pow
const n = xArr.length - 1
return xArr.reduce((sum, x, i) => sum + c(n, i) * x * p(rt, n - i) * p(t, i), 0)
// 组合C(5 2)
function c(n: number, i: number) {
if (n / 2 < i) i = n - i
let cs = 1, bcs = 1
while (i > 0) {
cs *= n--
bcs *= i--
}
return cs / bcs
}
}
贝塞尔补间动画:
三阶贝塞尔动画曲线:
动画曲线为ease: cubic-bezier(0.25,0.1,0.25,1)
P0与P3为固定值,P1与P2为曲线控制点
X轴TIME为动画时间,Y轴PROGRESSION为动画进度
type controlPoint = [number, number, number, number]
/**
* 贝塞尔补间动画
* @param onFrame 动画帧回调方法,process为当前动画的进程百分比
* @param duration 动画所需时间,毫秒
* @param controlPoint 贝塞尔的两个控制点
* @return animate 返回对象,cancel方法取消动画,finished结合await等待动画结束
*/
function tween(onFrame: (process: number) => {}, duration?: number, controlPoint?: controlPoint) {
const [x1, y1, x2, y2] = controlPoint ?? Bezier.ease
const startT = Date.now()
let animateId
function cancel() {
if (animateId) {
cancelAnimationFrame(animateId)
animateId = undefined
}
}
let finish
const finished = new Promise((resolve) => {
finish = () => {
animateId = undefined
resolve()
}
})
function loop() {
const diff = Date.now() - startT
// 时间作为x
const x = diff / (duration ?? 300)
// 以x求得其对应贝塞尔公式的t
const t = solveCureX(Math.min(x, 1))
// t代入贝塞尔公式求得y方向上的动画进程
// process进程百分比
const process = sampleCurveY(t)
onFrame(process)
if (x < 1) animateId = requestAnimationFrame(loop)
else finish()
}
const ax = 3 * x1 - 3 * x2 + 1
const bx = 3 * x2 - 6 * x1
const cx = 3 * x1
/**
* 三阶贝塞尔简化公式,已知t求x方向的值
* p0固定为0,0
* p1为x1,y1
* p2为x2,y2
* p3固定为1,1
*/
function sampleCurveX(t: number) {
return ((ax * t + bx) * t + cx) * t
}
function sampleCurveDerivativeX(t: number) {
// 霍纳规则
return (3*ax*t + 2*bx)*t + cx
}
/**
* 三阶贝塞尔简化公式,已知t求y方向的值
*/
const ay = 3 * y1 - 3 * y2 + 1
const by = 3 * y2 - 6 * y1
const cy = 3 * y1
function sampleCurveY(t: number) {
return ((ay * t + by) * t + cy) * t
}
const ZERO_LIMIT = 1e-6
/**
* 已知x求t
*/
function solveCureX(x: number) {
let t2 = x
let derivative
let x2
// 首先尝试使用牛顿迭代法对 x 求 t 的近似解
for (let i = 0; i < 8; i++) {
x2 = sampleCurveX(t2) - x
if (Math.abs(x2) < ZERO_LIMIT) {
return t2
}
derivative = sampleCurveDerivativeX(t2)
if (Math.abs(derivative) < ZERO_LIMIT) {
break
}
t2 -= x2 / derivative
}
// 如果精度不满足,再用二分法逼近求得 t
let t0 = 0
let t1 = 1
t2 = x
while (t0 < t1) {
x2 = sampleCurveX(t2)
if (Math.abs(x2 - x) < ZERO_LIMIT) {
return t2
}
if (x > x2) {
t0 = t2
} else {
t1 = t2
}
t2 = (t1 - t0) / 2 + t0
}
return t2
}
loop()
return {
cancel,
finished
}
}