ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Remotion 场景转场实战:用 TransitionSeries 实现过渡动画、覆盖层与时长控制

Remotion 场景转场实战:用 TransitionSeries 实现过渡动画、覆盖层与时长控制 Remotion 场景转场实战用 TransitionSeries 实现过渡动画、覆盖层与时长控制【免费下载链接】remotion Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotionTransitionSeries是 Remotion 中专门用于编排多场景 镜头切换的序列组件它把一段视频拆成若干TransitionSeries.Sequence并在两个场景的衔接点叠加Transition过渡或Overlay覆盖层前者让两段画面同时播放从而产生淡入淡出、滑动、擦除等转场后者在不动时间线总长度的前提下渲染光晕、文字等效果。读完本文你将掌握remotion/transitions的安装与基本写法、Transition 与 Overlay 的核心 Props、两类元素混排时的约束规则以及如何精确计算过渡后的成片总时长。本文主体对应仓库中的技能文档 transitions.md实现细节以remotion/transitions包源码TransitionSeries.tsx 等为佐证。为什么需要 TransitionSeries在传统 Remotion 写法中场景之间通常是硬切前一个Sequence播放完最后一帧下一个Sequence立刻开始。要实现淡入淡出等影视级转场需要自行在时间轴上重叠两个场景并同步处理透明度或位移代码繁琐且容易出错。TransitionSeries正是为这一需求设计的容器组件。从其实现看它最终渲染为一个普通的 RemotionSequenceTransitionSeries.tsx默认使用absolute-fill布局TransitionSeries.tsx所有子元素都是绝对定位彼此铺满全屏叠加渲染。它接受三类子组件TransitionSeries.Sequence一段独立场景必须声明durationInFramesTransitionSeries.Transition定义两段场景之间的过渡效果会让时间线缩短TransitionSeries.Overlay定义覆盖在切换点之上的效果不改变时间线总长。这三个子组件与主组件通过静态属性挂接TransitionSeries.tsx因此必须写作TransitionSeries.Sequence、TransitionSeries.Transition、TransitionSeries.Overlay的形式。前置条件安装TransitionSeries属于独立的包remotion/transitions官方技能文档建议在项目根目录执行npx remotion add remotion/transitions如果你希望手动安装到 monorepo 或自定义工程中也可以在packages/transitions的 package.json 中找到该包完整的信息与导出表exports字段明确定义了remotion/transitions/fade、/slide、/wipe、/flip、/clock-wipe等按需子路径。Transition 基础示例给两个场景加一个淡入淡出最经典的用法是在两段场景之间插入一个TransitionSeries.Transition。以下代码来自技能文档的核心示例两段各 60 帧的场景之间加一个持续 15 帧的线性淡入淡出。import { TransitionSeries, linearTiming } from remotion/transitions; import { fade } from remotion/transitions/fade; TransitionSeries TransitionSeries.Sequence durationInFrames{60} SceneA / /TransitionSeries.Sequence TransitionSeries.Transition presentation{fade()} timing{linearTiming({ durationInFrames: 15 })} / TransitionSeries.Sequence durationInFrames{60} SceneB / /TransitionSeries.Sequence /TransitionSeries;在这里presentation描述长什么样这里是fade()timing描述多快、怎么加速这里是linearTiming匀速、共 15 帧。由于过渡期间两段画面同时播放前段退出、后段进入总时长会小于各段之和——具体算法见下文时长计算一节。从源码角度解释机制TransitionSeries会递归遍历平铺后的子元素TransitionSeries.tsx为每个 Scene 生成一个真实的Sequence并用presentation.component包裹前后两段画面。以fade()为例其组件内部对进入中的场景设opacity progress、对退出中的场景设opacity 1 - progressfade.tsx从而在两帧之间形成透明度交叉淡化。Overlay 基础示例在切换点叠加光晕效果如果你不想让两段画面在切换时重叠播放例如要保持时间线总长度不变、或者效果本身不需要场景叠化就使用TransitionSeries.Overlay。Overlay 渲染在切换点正上方任意 React 组件都可以作为覆盖层。技能文档提示需要一个现成效果时可参考本仓库的light-leaks主题。仓库中确实存在独立的效果包 light-leaks同时在remotion/effects中提供了可组合的 light-leak 效果。技能文档给出了用SolidlightLeak效果自建光晕覆盖层的完整写法import {lightLeak} from remotion/effects/light-leak; import {TransitionSeries} from remotion/transitions; import {interpolate, Solid, useCurrentFrame, useVideoConfig} from remotion; const LightLeakOverlay: React.FC () { const frame useCurrentFrame(); const {durationInFrames, height, width} useVideoConfig(); return ( Solid width{width} height{height} effects{[ lightLeak({ progress: interpolate(frame, [0, durationInFrames - 1], [0, 1], { extrapolateLeft: clamp, extrapolateRight: clamp, }), }), ]} / ); }; TransitionSeries TransitionSeries.Sequence durationInFrames{60} SceneA / /TransitionSeries.Sequence TransitionSeries.Overlay durationInFrames{20} LightLeakOverlay / /TransitionSeries.Overlay TransitionSeries.Sequence durationInFrames{60} SceneB / /TransitionSeries.Sequence /TransitionSeries;要点解析useCurrentFrame()与useVideoConfig()使覆盖层知道自己当前处于 Overlay 时间段的第几帧以及整个序列的总帧数interpolate(frame, [0, durationInFrames - 1], [0, 1], ...)把覆盖层的本地帧线性映射到0 → 1的进度区间并clamp两侧防止进度越界durationInFrames{20}表示光晕从切换点前 10 帧开始、到切换点后 10 帧结束以切点为中心居中。对照源码Overlay 被渲染为一个以Math.round(overlayFrom)为起点、以durationInFrames为长度的独立SequenceTransitionSeries.tsx其中overlayFrom cutPoint - durationInFrames / 2 offsetTransitionSeries.tsx。这就是 Overlay 默认以切点居中的由来。同时使用 Transition 与 OverlayTransition 和 Overlay 可以共存于同一个TransitionSeries中但布局上有硬性规则Overlay 不能紧邻 Transition也不能紧邻另一个 Overlay。换句话说Overlay 只允许出现在两个普通 Sequence 之间而 Transition 只允许出现在两个 Sequence或与作为其前后锚点的场景之间。技能文档中的完整示例是一个覆盖层与一个转场同时存在的三段式结构import {lightLeak} from remotion/effects/light-leak; import {TransitionSeries, linearTiming} from remotion/transitions; import {fade} from remotion/transitions/fade; import {interpolate, Solid, useCurrentFrame, useVideoConfig} from remotion; const LightLeakOverlay: React.FC () { const frame useCurrentFrame(); const {durationInFrames, height, width} useVideoConfig(); return ( Solid width{width} height{height} effects{[ lightLeak({ progress: interpolate(frame, [0, durationInFrames - 1], [0, 1], { extrapolateLeft: clamp, extrapolateRight: clamp, }), }), ]} / ); }; TransitionSeries TransitionSeries.Sequence durationInFrames{60} SceneA / /TransitionSeries.Sequence TransitionSeries.Overlay durationInFrames{30} LightLeakOverlay / /TransitionSeries.Overlay TransitionSeries.Sequence durationInFrames{60} SceneB / /TransitionSeries.Sequence TransitionSeries.Transition presentation{fade()} timing{linearTiming({ durationInFrames: 15 })} / TransitionSeries.Sequence durationInFrames{60} SceneC / /TransitionSeries.Sequence /TransitionSeries;此例中 Overlay30 帧夹在 SceneA 与 SceneB 之间、正好坐落在第一个切点上而 fade 过渡15 帧位于 SceneB 与 SceneC 之间。这些排列约束不是口头约定而是由运行时校验强制保证的。在 TransitionSeries.tsx 中只要检测到两个 Overlay 相邻、“Overlay 后面跟 Transition”或“Transition 后面跟 Overlay”就会直接抛出TypeError并给出具体是第几个子元素出错。此外Overlay 的durationInFrames必须是正整数TransitionSeries.tsxOverlay 的offset必须是有限整数、不能是NaNTransitionSeries.tsxOverlay 不能延伸到第 0 帧之前TransitionSeries.tsx也不能超出前一段或后一段 Sequence 的时长TransitionSeries.tsx 与 TransitionSeries.tsx任意一段 Sequence 的时长都不能短于它相邻的 Transition 时长否则抛错提示场景比转场还短TransitionSeries.tsx。在仓库的 overlay.test.tsx 与 transitions.test.tsx 中可找到大量针对上述规则的回归测试可作为你自行验证行为的参考。Transition 的 PropsTransitionSeries.Transition必填两个 prop类型定义见 types.tsProp必填说明presentation否源码中缺省时回退为slide()视觉效果如fade()、slide()、wipe()等timing是控制速度与缓动如linearTiming()、springTiming()需要说明的是虽然源码中presentation带默认回退值TransitionSeries.tsx但工程上仍建议始终显式传入presentation避免隐式行为。Overlay 的 PropsTransitionSeries.Overlay的完整类型如下types.tsProp类型说明durationInFramesnumberOverlay 可见的总帧数正整数offset?number相对切换点中心的前后偏移正值更晚出现负值更早出现默认0childrenReactNode覆盖层内容任意组件以默认值0为例Overlay 从切点前durationInFrames / 2帧开始到切点后durationInFrames / 2帧结束正好以切点为轴心居中。设置offset{-5}则整体左移 5 帧更早出现设置offset{5}则整体右移 5 帧更晚结束。可用转场类型一览技能文档列出的基础转场都通过各自独立子路径导入每个子路径在 package.json 的exports中有对应入口import { fade } from remotion/transitions/fade; import { slide } from remotion/transitions/slide; import { wipe } from remotion/transitions/wipe; import { flip } from remotion/transitions/flip; import { clockWipe } from remotion/transitions/clock-wipe;其中部分转场还从remotion/transitions主入口直接导出如crossZoom、dreamyZoom、filmBurn、linearBlur、pushCut见 index.ts。如果浏览 presentations 目录可以看到仓库实际内置了比上述基础集更丰富的现成转场实现包括book-flip、cross-zoom、crosswarp、dissolve、iris、linear-blur、none、push-cut、ripple、swap、zoom-blur、zoom-in-out等命名与 package.json 的导出子路径一一对应。例如在渲染酷炫的推拉切push cut或缩放模糊转场时可直接按子路径引用。带方向的滑动转场slide()支持通过direction指定新场景从哪个方向推入。技能文档的示例import { slide } from remotion/transitions/slide; TransitionSeries.Transition presentation{slide({ direction: from-left })} timing{linearTiming({ durationInFrames: 20 })} /;四个可选方向为from-leftfrom-rightfrom-topfrom-bottom方向的完整类型在 slide.tsx 中定义。其实现原理是进入的场景从屏幕外translateX(-100%)等移入退出的场景同时向另一侧移出从而形成旧画面被新画面推走的视觉效果slide.tsx。值得注意的一个实现细节源码在两块画面之间使用了epsilon0.01%修正量避免进度为 1 前两画面之间出现一条白色细缝slide.tsx。除direction外slide()还支持enterStyle与exitStyle两个可选 prop用于给进入/退出的场景附加自定义 CSS 样式slide.tsx。fade()同样支持enterStyle、exitStyle以及shouldFadeOutExitingScene是否让退出的旧场景也淡出默认旧场景保持不透明、只有新场景淡入见 fade.tsx。Timing 选项linearTiming 与 springTimingremotion/transitions提供两种内置 Timing 函数都满足TransitionTiming接口——该接口只要求两个方法getDurationInFrames({fps})返回转场总帧数getProgress({frame, fps})返回0 → 1的进度types.ts。import { linearTiming, springTiming } from remotion/transitions; // 线性 Timing —— 匀速运动 linearTiming({ durationInFrames: 20 }); // 弹簧 Timing —— 有机、带惯性的运动 springTiming({ config: { damping: 200 }, durationInFrames: 25 });linearTiming的实现最简单直接getDurationInFrames()直接返回传入的durationInFramesgetProgress()则用 Remotion 的interpolate把帧区间[0, durationInFrames]线性映射到[0, 1]并在两侧clamplinear-timing.ts。它额外接受一个可选的easing函数用来在匀速基础上叠加自定义缓动曲线。springTiming的选项更多源码签名如下spring-timing.tsconfig?: PartialSpringConfig弹簧物理参数damping、mass、stiffness等直接透传给 Remotion 的spring()durationInFrames?: number可选。若不传则由measureSpring()根据弹簧物理参数在给定fps下自动测算弹簧何时静止durationRestThreshold?: number静止判定阈值reverse?: boolean是否反向1 → 0。理解这一点非常重要springTiming不传durationInFrames时其实际时长依赖fps因为需要根据帧率计算弹簧衰减到静止所需的帧数getDurationInFrames({fps})只有在传入明确时长时才与 fps 无关spring-timing.ts。时长计算过渡会吃掉重叠帧这是使用TransitionSeries时最容易踩坑、也最需要理解的一点Transition 会缩短总时长相邻两段场景在过渡期间同时播放重叠部分只计一次Overlay 不影响总时长它只是铺在切换点上的一个图层不参与时间线总长度的叠加。用技能文档的例子说明两个各 60 帧的序列之间加一个 15 帧的转场无转场60 60 120帧有转场60 60 - 15 105帧。在两个序列之间夹入 Overlay 则不会改变这个总和。从渲染角度也能印证代码在计算每个 Sequence 的起始帧时会把前面所有 Transition 的时长作为负偏移累计进transitionOffsets导致后续场景整体前移TransitionSeries.tsx而 Overlay 则在所有 Sequence 排布结束后、作为独立Sequence追加渲染因而完全不占用场景时长TransitionSeries.tsx。用 getDurationInFrames() 读取单个转场时长由于过渡帧数是被吃掉的量你随时可以在 Timing 对象上调用getDurationInFrames({fps})精确取值import { linearTiming, springTiming } from remotion/transitions; const linearDuration linearTiming({ durationInFrames: 20, }).getDurationInFrames({ fps: 30 }); // Returns 20 const springDuration springTiming({ config: { damping: 200 }, }).getDurationInFrames({ fps: 30 }); // Returns calculated duration based on spring physics第一个例子返回 20linearTiming时长固定fps 不影响第二个例子因为springTiming未指定durationInFrames返回的是在fps 30下根据弹簧物理测算出的静止帧数因此当你把项目帧率从 30 改为 60 时这个值通常也会改变。手动计算整段合成时长知道每个转场各自吃掉了多少帧就可以手工推算整段成片的总时长。技能文档给出三场景、双转场的完整推导import { linearTiming } from remotion/transitions; const scene1Duration 60; const scene2Duration 60; const scene3Duration 60; const timing1 linearTiming({ durationInFrames: 15 }); const timing2 linearTiming({ durationInFrames: 20 }); const transition1Duration timing1.getDurationInFrames({ fps: 30 }); const transition2Duration timing2.getDurationInFrames({ fps: 30 }); const totalDuration scene1Duration scene2Duration scene3Duration - transition1Duration - transition2Duration; // 60 60 60 - 15 - 20 145 frames即总时长 所有 Sequence 时长之和 − 所有 Transition 时长之和。写成通用公式就是totalDuration Σ(Sequence.durationInFrames) − Σ(Transition.getDurationInFrames({fps}))Overlay 的durationInFrames不参与上式的任何加减。若某段 Sequence 使用了非默认的offset还需把这部分偏移一并计入起止帧这一点在 TransitionSeries.tsx 的起始帧累加逻辑中有体现。实践时建议直接用一个常量函数或useMemo汇总计算结果并把 fps 统一传入避免在制作过程中因改动帧率导致画面与进度不同步。进阶useTransitionProgress 与自定义转场除TransitionSeries本体外remotion/transitions还导出一个非常实用的 HookuseTransitionProgress()use-transition-progress.ts。在任意TransitionSeries.Sequence的子组件内部调用它可以拿到当前正处于转场中的进度状态import {useTransitionProgress} from remotion/transitions; const MyScene () { const {entering, exiting, isInTransitionSeries} useTransitionProgress(); // entering / exiting 为 0~1 的进度值 return null; };返回值TransitionState包含三个字段use-transition-progress.tsentering本场景从上一个转场进入的进度未处于转场时为1exiting本场景向下一个转场退出的进度未处于转场时为0isInTransitionSeries当前是否确实处于TransitionSeries的转场上下文中。当组件处于转场重叠区间时TransitionSeries会把进度通过 React ContextEnteringContext/ExitingContext注入子树TransitionSeries.tsxuseTransitionProgress只是读取该 Context 的便捷封装。你可以据此让场景内的元素比如标题、字幕、角色随转场进度联动位移或缩放而不是只依赖 presentation 对整个场景做整体变换。更进一步若要打造完全自定义的转场可自行构造TransitionPresentation{component, props}结构见 types.ts或将已有转场配合 Remotion 内置能力扩展。所有内置 presentation 的源码presentations 目录都是现成的学习范本。小结TransitionSeries把场景编排 镜头切换 特效叠加收拢为一个声明式组件模型核心心智可归纳为三句话场景用TransitionSeries.Sequence依次排列时长各自声明想让两段画面叠着切在两个 Sequence 之间放TransitionSeries.Transitionpresentationtiming它会缩短总时长想在不缩短时间线的前提下把光晕、字幕等盖在切换点上就把TransitionSeries.Overlay放在两个普通 Sequence 之间注意它不能紧邻 Transition 或其他 Overlay。配合linearTiming/springTiming的getDurationInFrames({fps})精确核算总时长再叠加useTransitionProgress()做场景内元素的同步动画即可在 Remotion 中以纯代码产出具备专业剪辑感的视频。更多源码级细节可继续研读 TransitionSeries.tsx、types.ts 以及配套的 测试用例。【免费下载链接】remotion Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表