我如何从零开始为 Excalidraw 构建 “Magic Move” 动画引擎并发布
Source: Dev.to
目标
我喜欢使用 Excalidraw 来绘制系统架构草图。但草图是静态的。当我想展示数据包如何在负载均衡器中流动,或数据库分片如何拆分时,我只能手忙脚乱地挥手,或者制作 10 张不同的幻灯片。
我想要一种 “绘制逻辑,导出动画” 的能力。
我不想要时间轴编辑器(比如 After Effects),那对一个简单的图表来说工作量太大。
我想要 “无键帧动画”:
- 绘制 帧 1(起始状态)。
- 将其克隆为 帧 2。
- 将元素移动到新位置。
- 引擎会自动算出过渡效果。
我使用 Next.js、Excalidraw 和 Framer Motion 构建了这个引擎。下面是实现逻辑的技术深度剖析。
1. 核心逻辑:状态差分
最难的部分不是动画循环,而是 差分。当我们从 帧 A 迁移到 帧 B 时,需要通过元素的稳定 ID 来识别它们,并将它们归入以下三类:
- 稳定(Stable):元素在两个帧中都存在(需要变形/移动)。
- 进入(Entering):仅在 B 中存在(需要淡入)。
- 退出(Exiting):仅在 A 中存在(需要淡出)。
我编写了一个 categorizeTransition 工具函数,用于高效映射元素:
// Simplified logic from src/utils/editor/transition-logic.ts
export function categorizeTransition(prevElements, currElements) {
const stable = [];
const morphed = [];
const entering = [];
const exiting = [];
const prevMap = new Map(prevElements.map(e => [e.id, e]));
const currMap = new Map(currElements.map(e => [e.id, e]));
// 1. Find Morphs (Stable) & Entering
currElements.forEach(curr => {
if (prevMap.has(curr.id)) {
const prev = prevMap.get(curr.id);
// Separate "Stable" (identical) from "Morphed" (changed)
// to optimize the render loop
if (areVisuallyIdentical(prev, curr)) {
stable.push({ key: curr.id, element: curr });
} else {
morphed.push({ key: curr.id, start: prev, end: curr });
}
} else {
entering.push({ key: curr.id, end: curr });
}
});
// 2. Find Exiting
prevElements.forEach(prev => {
if (!currMap.has(prev.id)) {
exiting.push({ key: prev.id, start: prev });
}
});
return { stable, morphed, entering, exiting };
}
2. 插值属性
对于 Morphed(变形)元素,需要在任意 progress(0.0 → 1.0)时计算其中间状态。
- 数值(x、y、width):线性插值即可。
- 颜色(strokeColor):先将十六进制转为 RGBA,分别插值每个通道,再转回。
- 角度:使用 “最短路径” 插值。
如果对象从 10° 旋转到 350°,线性插值会走很长的弧线。下面的辅助函数会找到最短的旋转方向:
// src/utils/smart-animation.ts
const angleProgress = (oldAngle, newAngle, progress) => {
let diff = newAngle - oldAngle;
// Normalize to -π to +π to find shortest direction
while (diff > Math.PI) diff -= 2 * Math.PI;
while (diff {
return 1 - Math.pow(1 - t, 4);
};
这确保了大型架构块能够带着重量“砰砰”落位,而不是像幽灵一样滑来滑去。
接下来
我目前正在开发:
- 子步骤动画:允许在单个帧内部通过点击逐条展示要点。
- 导出为 MP4:直接将画布流录制为视频文件。
项目已上线,我构建它的初衷是帮助开发者更好地沟通。
点此尝试:
免费 Stripe 促销码:postara
欢迎分享你对这种方法的看法!