Motion Canvas 中的数学:动画曲线与插值函数原理
在动画创作中,流畅自然的运动效果往往依赖于精妙的数学计算。Motion Canvas 作为一款基于代码的动画创作工具,其核心引擎通过数学函数控制动画的时间变化率和属性过渡过程。本文将深入解析 Motion Canvas 中的动画曲线(Timing Functions)与插值函数(Interpolation Functions)原理,帮助创作者理解动画背后的数学逻辑,从而实现更精准的视觉表达。
动画曲线:时间与运动的数学关系
动画曲线定义了动画在时间轴上的速度变化规律。Motion Canvas 在 packages/core/src/tweening/timingFunctions.ts 文件中实现了多种经典动画曲线,这些函数接收归一化时间值(0到1)并返回调整后的进度值,从而控制动画的加速、减速等运动特性。
基础动画曲线类型
Motion Canvas 提供了10类基础动画曲线,每类包含入(In)、出(Out)和入出(InOut)三种变化形式:
| 曲线类型 | 数学特征 | 应用场景 |
|---|---|---|
| Sine(正弦) | 基于三角函数的平滑过渡 | 自然缓动效果 |
| Quad(二次方) | t² 多项式曲线 | 简单加速/减速 |
| Cubic(三次方) | t³ 多项式曲线 | 更强的加速效果 |
| Quart(四次方) | t⁴ 多项式曲线 | 剧烈的速度变化 |
| Quint(五次方) | t⁵ 多项式曲线 | 极快的启动/停止 |
| Expo(指数) | 2^t 指数曲线 | 电子设备开关效果 |
| Circ(圆形) | √(1-t²) 平方根曲线 | 平滑的边缘过渡 |
| Back(回退) | 带超调的三次曲线 | 弹性物体碰撞效果 |
| Elastic(弹性) | 衰减正弦曲线 | 弹簧振动效果 |
| Bounce(弹跳) | 分段二次曲线 | 物体落地弹跳效果 |
核心实现原理
所有动画曲线函数都遵循相同的接口定义,接收归一化时间值并返回调整后的进度值:
export interface TimingFunction {
(value: number, from?: number, to?: number): number;
}
以最基础的二次方曲线为例,其"入"(加速)形式实现如下:
export function easeInQuad(value: number, from = 0, to = 1): number {
value = value * value; // t² 加速曲线
return map(from, to, value);
}
而"出"(减速)形式则通过反转计算实现:
export function easeOutQuad(value: number, from = 0, to = 1): number {
value = 1 - Math.pow(1 - value, 2); // 1-(1-t)² 减速曲线
return map(from, to, value);
}
插值函数:属性过渡的数学桥梁
插值函数负责计算两个关键帧之间的中间值,是实现平滑动画过渡的核心。Motion Canvas 在 packages/core/src/tweening/interpolationFunctions.ts 中实现了多种数据类型的插值算法,支持数字、字符串、向量、颜色等复杂属性的平滑过渡。
基础插值算法
线性插值(Linear Interpolation)
线性插值是最基础的过渡算法,其数学公式为:value = from + (to - from) * t。在代码中实现为:
export function map(from: number, to: number, value: number) {
return from + (to - from) * value;
}
该函数在 easeLinear 动画曲线中直接使用,实现匀速运动效果。
向量插值
对于位置、尺寸等向量属性,Motion Canvas 通过 Vector2.lerp 方法实现二维向量插值:
// 假设 Vector2 类实现
class Vector2 {
x: number;
y: number;
lerp(other: Vector2, value: number): Vector2 {
return new Vector2(
map(this.x, other.x, value),
map(this.y, other.y, value)
);
}
}
复杂数据类型插值
文本插值
文本插值通过逐步替换字符实现打字机效果,其核心逻辑在 textLerp 函数中实现:
export function textLerp(fromString: string, toString: string, value: number) {
const from = [...fromString];
const to = [...toString];
// 根据目标长度动态计算当前显示文本
if (to.length >= from.length) {
const current = Math.floor(to.length * value);
let text = '';
for (let i = 0; i < to.length; i++) {
if (i < current) {
text += to[i]; // 已过渡字符
} else if (from[i] || i <= currentLength) {
text += from[i] ?? to[i]; // 过渡中字符
}
}
return text;
}
// 处理目标文本较短的情况...
}
深度插值(Deep Interpolation)
深度插值支持对象、数组等复杂数据结构的递归插值,其实现逻辑在 deepLerp 函数中:
export function deepLerp<T>(from: T, to: T, value: number): T {
if (value === 0) return from;
if (value === 1) return to;
// 数字插值
if (typeof from === 'number' && typeof to === 'number') {
return map(from, to, value) as unknown as T;
}
// 对象插值
if (from && to && typeof from === 'object' && typeof to === 'object') {
if (Array.isArray(from) && Array.isArray(to) && from.length === to.length) {
return from.map((f, i) => deepLerp(f, to[i], value)) as unknown as T;
}
// Map/对象插值逻辑...
}
// 类型不匹配时直接跳转
return to;
}
动画系统的数学架构
Motion Canvas 的动画系统通过动画曲线与插值函数的协同工作,实现从时间进度到属性值的完整映射。其核心流程如下:
- 时间归一化:将动画持续时间内的实际时间转换为0到1的归一化时间值
- 曲线变换:通过动画曲线函数(如
easeOutCubic)调整时间进度,实现变速效果 - 属性插值:使用插值函数根据调整后的进度值计算属性中间值
数学函数协作示例
以下代码展示了一个完整的动画计算过程:
// 1. 获取归一化时间 (0-1)
const normalizedTime = currentTime / duration;
// 2. 应用动画曲线 (减速效果)
const easedTime = easeOutCubic(normalizedTime);
// 3. 计算属性插值 (位置变化)
const newPosition = deepLerp(startPosition, endPosition, easedTime);
实践应用:自定义动画曲线
Motion Canvas 允许通过组合基础数学函数创建自定义动画曲线。例如,创建一个带微小弹性的减速曲线:
function customEaseOut(value: number): number {
// 基础减速曲线 + 微小弹性
const bounce = Math.sin(value * 3) * Math.pow(1 - value, 2) * 0.2;
return easeOutQuad(value) + bounce;
}
通过调整数学参数,可以精确控制动画的"弹性"强度和振动频率,实现独特的运动效果。
总结与展望
Motion Canvas 将复杂的动画数学抽象为直观的函数接口,既保证了数学计算的精确性,又为创作者提供了灵活的控制手段。从简单的线性移动到复杂的弹性碰撞,从单一属性过渡到多层次的场景动画,其核心都建立在坚实的数学基础之上。
随着项目的发展,Motion Canvas 可能会引入更多高级数学模型,如基于物理的动画系统或机器学习驱动的运动预测。掌握这些数学原理,将帮助创作者突破工具限制,实现更具创意的视觉表达。
要深入学习 Motion Canvas 动画系统,建议阅读以下源代码文件:
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



