Remotion 教程 06:Motion 的安全使用边界
前面几篇一直强调:Remotion 的主动画应该由 frame 推导出来。那问题来了:很多前端熟悉 Motion,也就是以前常说的 Framer Motion。它能不能放进 Remotion 视频里?
答案不是“完全不能用”,而是要分清边界:
Remotion 控制时间
Motion 负责表现为什么不能让 Motion 接管时间
Motion 是 Web UI 动画库。它很擅长处理按钮、弹窗、hover、拖拽、布局变化和页面转场。
这些能力在网页里很好用,因为网页动画通常依赖浏览器运行时:
- 浏览器真实时间经过了多久。
- 鼠标是否悬停。
- 用户是否点击或拖拽。
- DOM 布局测量结果有没有变化。
- 元素是否进入视口。
但 Remotion 渲染视频时关心的是另一件事:
第 0 帧应该是什么画面?
第 1 帧应该是什么画面?
第 2 帧应该是什么画面?所以在 Remotion Composition 里,主动画必须能回答“任意一帧是什么状态”。如果动画过程藏在 Motion 的运行时里,视频渲染就很难保证每一帧都可复现。
安全写法:frame 算值,Motion 接收 style
安全写法不是让 Motion 自己跑动画,而是让 Remotion 先算好这一帧的样式值:
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - 24,
fps,
config: { damping: 100 },
});
const opacity = interpolate(frame, [24, 44], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const rotation = interpolate(progress, [0, 1], [-8, 0]);
const x = interpolate(progress, [0, 1], [-80, 0]);这里有四个关键值:
frame:当前正在渲染的帧号。progress:由 Remotion 的spring()算出的动画进度。opacity:由 frame 映射出来的透明度。rotation和x:由 progress 映射出来的旋转角度和位移。
然后把这些值交给 motion.div:
<motion.div
style={{
opacity,
scale: progress,
rotate: `${rotation}deg`,
x,
}}
>
安全用法
</motion.div>注意这里没有写 animate,也没有写 transition={{ duration: 1 }}。Motion 只是一个能接收 transform、scale、rotate 等样式的组件。真正决定动画进度的仍然是 Remotion。
不推荐写法:Motion 自己跑动画
在网页里,你可能会这样写:
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
/>这段代码适合网页交互,但不适合作为 Remotion 视频里的主动画。原因是 duration: 1 表达的是运行时动画过程,而不是明确的帧状态。
对视频来说,更好的写法是:
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
return <motion.div style={{ opacity }} />;这样第 0 帧、第 15 帧、第 30 帧分别是什么透明度,都能直接算出来。
完整最小示例
下面是一个安全使用 Motion 的 Remotion 组件:
import React from "react";
import { motion } from "motion/react";
import {
AbsoluteFill,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from "remotion";
export const SafeMotionDemo: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - 24,
fps,
config: { damping: 100 },
});
const opacity = interpolate(frame, [24, 44], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const rotation = interpolate(progress, [0, 1], [-8, 0]);
const x = interpolate(progress, [0, 1], [-80, 0]);
return (
<AbsoluteFill
style={{
backgroundColor: "#f8fafc",
padding: 96,
fontFamily: "Inter, sans-serif",
}}
>
<h1 style={{ margin: 0, fontSize: 72 }}>
Remotion 控制时间,Motion 呈现样式
</h1>
<motion.div
style={{
marginTop: 120,
width: 420,
padding: 40,
borderRadius: 8,
backgroundColor: "#16a34a",
color: "white",
opacity,
scale: progress,
rotate: `${rotation}deg`,
x,
}}
>
<h2 style={{ margin: 0, fontSize: 42 }}>安全用法</h2>
<p style={{ fontSize: 28, lineHeight: 1.4 }}>
Motion 不计算时间,只接收这一帧的样式值。
</p>
</motion.div>
</AbsoluteFill>
);
};这个示例里,Motion 只是表现层。时间仍然来自:
frame -> spring / interpolate -> style哪些 Motion 能力要避开
放在 Remotion Composition 里时,下面这些能力不适合作为视频主动画:
animatetransition.durationlayout/layoutIdwhileHover/whileTapdragwhileInView- scroll 驱动动画
它们的问题不是“库不好”,而是这些能力的设计目标是网页交互,不是逐帧视频渲染。
Motion 适合放在哪里
Motion 很适合放在视频生成产品的网页界面里,例如:
- 模板选择面板。
- 参数表单。
- 弹窗和抽屉。
- 拖拽排序。
- 预览页里的交互反馈。
如果放进 Remotion Composition,建议只把它当作表现层。
判断标准
你可以用一个问题判断写法是否安全:
我能不能只给定 frame,就算出这一帧的最终样式?如果答案是能,通常适合 Remotion。
如果答案是不能,需要等待浏览器播放、等待用户交互、等待布局动画完成,那它就不应该成为视频主动画。
示例项目位置
这篇文章对应示例项目里的:
Composition: LessonMotionBoundary
文件: src/lessons/MotionBoundary.tsx运行示例项目后,可以暂停在第 20 帧、第 40 帧、第 70 帧,观察 opacity、scale、rotate、x 是否都能由当前帧固定下来。
下一篇进入更广的生态选择:Lottie、Rive、Three、Skia、D3 分别适合什么类型的视频。