帧动画与Lottie实现
帧动画与 Lottie 两类低交互桌宠渲染方案的 TS 语义接口与依赖清单
帧动画与Lottie实现
摘要:帧动画与 Lottie 同属「低成本动态」层——只解决动作播放、不解决分部位交互。本文给出两者的渲染原理、TS 语义接口与依赖清单,并给出统一的渲染抽象,说明二者如何作为装饰层与降级方案接入桌宠架构。
总论将桌宠交互分为「能动、能应、能变」三层,帧动画与 Lottie 都止步于「能动」。二者共性在于把动画当作不可分解的整组片段:帧动画预先烘焙好每一帧位图,Lottie 预先烘焙好矢量插值轨迹,运行时都只有「播放整段」这一个自由度。明确这一边界,是正确使用它们的前提——它们适合入场动画、待机装饰、按钮反馈,不适合需要部位级反馈的本体。
帧动画的核心是精灵图裁剪。美术把每个动作的帧按网格排布合并为一张 PNG,渲染循环用 drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) 从图集中裁出当前帧绘到画布,或等价地用 CSS background-position 配合 animation: play 1s steps(8) 让 DOM 元素逐帧切换。前者适合 Canvas 场景,后者适合轻量 DOM 场景,两者共用同一份帧表(每帧的 x/y/w/h 与动作-帧区间映射)。性能开销仅是位图拷贝,是四方案中最低的。
帧动画的 TS 语义接口如下。依赖为零——仅原生 Canvas2D 与 DOM 类型。
// 依赖:无(原生 Canvas2D / DOM CSS)
type Frame = { x: number; y: number; w: number; h: number };
interface SpriteAnimator {
/** 载入精灵图与帧表,帧表形如 { idle: Frame[], walk: Frame[] } */
load(sheet: HTMLImageElement, actions: Record<string, Frame[]>): void;
/** 切换到指定动作并从头播放 */
play(action: string): void;
/** 设置全局帧率,默认 8~12 fps 即可观感自然 */
setFps(fps: number): void;
/** 动作播放完一圈的回调(循环动作不会触发) */
onLoopEnd(cb: (action: string) => void): void;
/** 由 rAF 驱动,dt 为毫秒时间增量 */
update(dt: number): void;
destroy(): void;
}
Lottie 的核心是把动画从代码中剥离。美术在 AE 中用形状层与关键帧制作动画,经 Bodymovin 插件导出 JSON(矢量路径 + 贝塞尔插值 + 时间轴),前端 lottie.loadAnimation({ path, renderer: 'svg' | 'canvas' }) 解析后自行渲染。矢量数据体积极小、任意缩放不失真,且插值在 AE 侧完成,运行时无动画逻辑。renderer 选择上,SVG 便于 DOM 集成与样式覆盖,Canvas 适合大量实例。
Lottie 的 TS 语义接口如下。
// 依赖:lottie-web(含类型声明)
type LottieRenderer = 'svg' | 'canvas';
interface LottiePlayer {
/** 载入 JSON 描述文件或对象 */
load(json: string | Record<string, unknown>, opts?: { renderer: LottieRenderer }): void;
play(): void;
pause(): void;
stop(): void;
/** 跳转到指定帧(isFrame 为 false 时按 0~1 比例) */
goToAndStop(frame: number, isFrame?: boolean): void;
/** 单次播放结束回调,用于串联后续动作 */
onComplete(cb: () => void): void;
destroy(): void;
}
两者能力边界一致:动作不可分部位控制,交互只能「整组切换」。差异在制作链:帧动画由前端拼图、Lottie 由 AE 动效师产出,团队无动效人力时帧动画反而更快;有动效人力时 Lottie 的丝滑度与体积全面胜出。接入桌宠架构时,二者应作为 PetRenderer 统一抽象的两种实现,承担入场、徽章、气泡等装饰片段:
/** 统一渲染抽象:所有方案均实现此契约 */
interface PetRenderer {
mount(container: HTMLElement): void;
update(dt: number): void;
destroy(): void;
}
实现上的注意点:帧动画的精灵图应按动作分组维护帧表而非按时间轴平铺,便于复用与增删动作;Lottie 的 JSON 应走资源缓存并预解析,避免首帧卡顿;两者都可用 prefers-reduced-motion 降级为静态首帧。展望:帧动画可扩展换装(同一动作多套精灵图热切换),Lottie 可配合 goToAndStop 做简单的点击分段反馈,但上限仍在「整组播放」,超出即应升级至 Live2D 或 3D 方案。