Files
esengine/packages/framework/math/src/Animation/Easing.ts

465 lines
11 KiB
TypeScript
Raw Normal View History

2025-08-10 16:00:02 +08:00
/**
*
*
2025-08-10 16:00:02 +08:00
*
* t (0-1) (0-1)
*/
export class Easing {
// 线性缓动
/**
2025-08-10 16:00:02 +08:00
* 线
* @param t (0-1)
* @returns
*/
static linear(t: number): number {
return t;
}
2025-08-10 16:00:02 +08:00
// 二次方缓动 (Quadratic)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quadIn(t: number): number {
return t * t;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quadOut(t: number): number {
return 1 - (1 - t) * (1 - t);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quadInOut(t: number): number {
return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
}
2025-08-10 16:00:02 +08:00
// 三次方缓动 (Cubic)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static cubicIn(t: number): number {
return t * t * t;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static cubicOut(t: number): number {
return 1 - Math.pow(1 - t, 3);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static cubicInOut(t: number): number {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
2025-08-10 16:00:02 +08:00
// 四次方缓动 (Quartic)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quartIn(t: number): number {
return t * t * t * t;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quartOut(t: number): number {
return 1 - Math.pow(1 - t, 4);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quartInOut(t: number): number {
return t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
}
2025-08-10 16:00:02 +08:00
// 五次方缓动 (Quintic)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quintIn(t: number): number {
return t * t * t * t * t;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quintOut(t: number): number {
return 1 - Math.pow(1 - t, 5);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static quintInOut(t: number): number {
return t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
}
2025-08-10 16:00:02 +08:00
// 正弦缓动 (Sine)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static sineIn(t: number): number {
return 1 - Math.cos((t * Math.PI) / 2);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static sineOut(t: number): number {
return Math.sin((t * Math.PI) / 2);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static sineInOut(t: number): number {
return -(Math.cos(Math.PI * t) - 1) / 2;
}
2025-08-10 16:00:02 +08:00
// 指数缓动 (Exponential)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static expoIn(t: number): number {
return t === 0 ? 0 : Math.pow(2, 10 * (t - 1));
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static expoOut(t: number): number {
return t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static expoInOut(t: number): number {
if (t === 0) return 0;
if (t === 1) return 1;
2025-08-10 16:00:02 +08:00
return t < 0.5
? Math.pow(2, 20 * t - 10) / 2
: (2 - Math.pow(2, -20 * t + 10)) / 2;
}
// 圆形缓动 (Circular)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static circIn(t: number): number {
return 1 - Math.sqrt(1 - t * t);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static circOut(t: number): number {
return Math.sqrt(1 - (t - 1) * (t - 1));
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static circInOut(t: number): number {
return t < 0.5
? (1 - Math.sqrt(1 - 4 * t * t)) / 2
: (Math.sqrt(1 - (-2 * t + 2) * (-2 * t + 2)) + 1) / 2;
}
2025-08-10 16:00:02 +08:00
// 回弹缓动 (Back)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @param s 1.70158
* @returns
*/
static backIn(t: number, s: number = 1.70158): number {
const c1 = s;
const c3 = c1 + 1;
return c3 * t * t * t - c1 * t * t;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @param s 1.70158
* @returns
*/
static backOut(t: number, s: number = 1.70158): number {
const c1 = s;
const c3 = c1 + 1;
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @param s 1.70158
* @returns
*/
static backInOut(t: number, s: number = 1.70158): number {
const c1 = s;
const c2 = c1 * 1.525;
2025-08-10 16:00:02 +08:00
return t < 0.5
? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
: (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
}
2025-08-10 16:00:02 +08:00
// 弹性缓动 (Elastic)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @param amplitude 1
* @param period 0.3
* @returns
*/
static elasticIn(t: number, amplitude: number = 1, period: number = 0.3): number {
if (t === 0) return 0;
if (t === 1) return 1;
2025-08-10 16:00:02 +08:00
const s = period / 4;
return -(amplitude * Math.pow(2, 10 * (t - 1)) * Math.sin((t - 1 - s) * (2 * Math.PI) / period));
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @param amplitude 1
* @param period 0.3
* @returns
*/
static elasticOut(t: number, amplitude: number = 1, period: number = 0.3): number {
if (t === 0) return 0;
if (t === 1) return 1;
2025-08-10 16:00:02 +08:00
const s = period / 4;
return amplitude * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / period) + 1;
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @param amplitude 1
* @param period 0.45
* @returns
*/
static elasticInOut(t: number, amplitude: number = 1, period: number = 0.45): number {
if (t === 0) return 0;
if (t === 1) return 1;
const s = period / 4;
if (t < 0.5) {
return -0.5 * (amplitude * Math.pow(2, 10 * (2 * t - 1)) * Math.sin((2 * t - 1 - s) * (2 * Math.PI) / period));
}
2025-08-10 16:00:02 +08:00
return amplitude * Math.pow(2, -10 * (2 * t - 1)) * Math.sin((2 * t - 1 - s) * (2 * Math.PI) / period) * 0.5 + 1;
2025-08-10 16:00:02 +08:00
}
// 跳跃缓动 (Bounce)
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static bounceIn(t: number): number {
return 1 - Easing.bounceOut(1 - t);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static bounceOut(t: number): number {
const n1 = 7.5625;
const d1 = 2.75;
if (t < 1 / d1) {
return n1 * t * t;
} else if (t < 2 / d1) {
return n1 * (t -= 1.5 / d1) * t + 0.75;
} else if (t < 2.5 / d1) {
return n1 * (t -= 2.25 / d1) * t + 0.9375;
} else {
return n1 * (t -= 2.625 / d1) * t + 0.984375;
}
2025-08-10 16:00:02 +08:00
}
/**
2025-08-10 16:00:02 +08:00
*
* @param t (0-1)
* @returns
*/
static bounceInOut(t: number): number {
return t < 0.5
? (1 - Easing.bounceOut(1 - 2 * t)) / 2
: (1 + Easing.bounceOut(2 * t - 1)) / 2;
}
2025-08-10 16:00:02 +08:00
// 组合缓动
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param easingFunctions
* @param weights
* @returns
*/
static combine(
easingFunctions: ((t: number) => number)[],
weights?: number[]
): (t: number) => number {
if (!weights) {
weights = new Array(easingFunctions.length).fill(1 / easingFunctions.length);
}
2025-08-10 16:00:02 +08:00
return (t: number): number => {
let result = 0;
for (let i = 0; i < easingFunctions.length; i++) {
result += easingFunctions[i](t) * (weights![i] || 0);
}
return result;
};
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param segments {duration, easing}
* @returns
*/
static piecewise(segments: Array<{duration: number; easing: (t: number) => number}>): (t: number) => number {
2025-08-10 16:00:02 +08:00
// 计算总持续时间
const totalDuration = segments.reduce((sum, seg) => sum + seg.duration, 0);
// 归一化持续时间
const normalizedSegments = segments.map((seg) => ({
...seg,
duration: seg.duration / totalDuration
}));
return (t: number): number => {
let accumulatedTime = 0;
for (const segment of normalizedSegments) {
if (t <= accumulatedTime + segment.duration) {
const localT = (t - accumulatedTime) / segment.duration;
return segment.easing(Math.max(0, Math.min(1, localT)));
}
accumulatedTime += segment.duration;
}
// 如果超出范围,返回最后一段的结束值
return normalizedSegments[normalizedSegments.length - 1].easing(1);
};
}
/**
2025-08-10 16:00:02 +08:00
*
* @param easing
* @returns
*/
static reverse(easing: (t: number) => number): (t: number) => number {
return (t: number): number => 1 - easing(1 - t);
}
2025-08-10 16:00:02 +08:00
/**
2025-08-10 16:00:02 +08:00
*
* @param easing
* @returns
*/
static mirror(easing: (t: number) => number): (t: number) => number {
return (t: number): number => {
if (t < 0.5) {
return easing(t * 2);
} else {
return easing(2 - t * 2);
}
};
}
// 常用预设
/** 平滑进入常用于UI动画 */
static readonly smoothIn = Easing.quadOut;
/** 平滑退出常用于UI动画 */
static readonly smoothOut = Easing.quadIn;
/** 快速进入(常用于出现动画) */
static readonly quickIn = Easing.cubicOut;
/** 快速退出(常用于消失动画) */
static readonly quickOut = Easing.cubicIn;
/** 自然运动(模拟物理) */
static readonly natural = Easing.quartOut;
/** 强调效果(吸引注意力) */
static readonly emphasize = Easing.backOut;
}