通过以特定方式编写 Remotion 标记,Remotion Studio 能够识别代码结构并使其具有交互性:
- 允许通过单击选择项目
- 允许拖放、调整大小和旋转
- 编辑 CSS 样式
- 使关键帧和缓动值可编辑
如果标记对于 Studio 来说过于复杂而无法使其交互,则这些值将变为灰色显示。
优先使用具有自己时间线的交互式组件
对于填充其父容器的组件,使用 Interactive.withSchema({wrapInSequence: true, layout: 'absolute-fill'})。该包装器提供其视觉根和时间线项,并默认裁剪溢出,因此内部组件可以返回一个片段。将时间、预挂载、维度和每个实例的样式放在导出的组件上。
将属于一起的视觉元素保持在同一个组件中,以便它们一起移动和修剪。内部布局容器仍然可以提供对齐、在较小区域内裁剪或动画组;仅填充场景的容器通常是不必要的。卡、文本框和具有自身边界的 SVG 可以保留默认的 layout: 'none' 并将其 style 转发到其视觉根。
import {Video} from '@remotion/media';
import type React from 'react';
import {Interactive, useVideoConfig, type InteractivitySchema} from 'remotion';
type SceneProps = {
readonly title: string;
};
const SceneInner: React.FC<SceneProps> = ({title}) => {
const {fps} = useVideoConfig();
return (
<>
<Video
name="Background footage"
src="https://remotion.media/video.mp4"
premountFor={fps}
objectFit="cover"
style={{width: '100%', height: '100%'}}
/>
<Interactive.Div
name="Scene title"
style={{
position: 'absolute',
left: 80,
bottom: 80,
color: 'white',
fontFamily: 'Helvetica, Arial, sans-serif',
fontSize: 80,
}}
>
{title}
</Interactive.Div>
</>
);
};
const sceneSchema = {
title: {type: 'string', default: '', description: 'Title'},
} as const satisfies InteractivitySchema;
export const Scene = Interactive.withSchema({
Component: SceneInner,
componentName: 'Scene',
schema: sceneSchema,
wrapInSequence: true,
layout: 'absolute-fill',
});
选择 Schema 字段
Schema 中的每个键都是一个 prop,Studio 可以在调用点读取和编辑。
不在 Schema 中的 Props 仍然有效,但在 Studio 中不可编辑。
键可以使用点表示法,例如 style.color。
| 类型 | 用途 | 可关键帧化 |
|---|---|---|
string | 字符串,例如标题、提示或代码片段 | 否 |
number | 数字,可带可选的 min、max、step、integer | 是 |
boolean | 开/关开关 | 是(保持) |
color | CSS 颜色字符串 | 是 |
enum | 在 variants 之间选择,每个变体都有自己的嵌套 schema | 可选(保持) |
array | 数字、颜色、枚举等的列表 | 否 |
asset | 媒体源,可带可选的 assetType | 否 |
font-family | CSS 字体系列 | 否 |
font-weight | 字体粗细 | 是 |
translate、scale、rotation-css、rotation-degrees、transform-origin | 变换 | 是 |
uv-coordinate | 元素上的标准化 [x, y] 点 | 是 |
svg-path | SVG 路径数据 | 是 |
remotion-captions | Caption[] 数据 | 否 |
hidden | 保持在控件之外的值 | — |
每个字段都需要一个 default,并且应该有一个 description,Studio 将其显示为标签。
设置 keyframable: false 以显示静态控件。
暴露内容和每个实例的外观,例如文本和强调色。
不要为 style.translate、style.scale、style.rotate、style.transformOrigin 和 style.opacity 添加字段:wrapInSequence: true 已经添加了它们。
要使根的 CSS 属性每个实例可编辑,展开内置的 schema 片段:
const cardSchema = {
...Interactive.textSchema, // style.color, style.fontSize, style.fontWeight, ...
...Interactive.backgroundSchema, // style.backgroundColor
...Interactive.borderRadiusSchema, // style.borderRadius, ...
} as const satisfies InteractivitySchema;
也可用:Interactive.borderSchema,以及用于接受字幕的组件的 Interactive.captionsSchema。
对于接受 React.ReactNode 子元素的组件,添加 ...Interactive.childrenSchema。
它为子内容提供控件,并且将来可能演变为支持更丰富的内容。
目前,它使用一个标有“Text”的字符串字段,默认值为 ''。
如果组件只需要字符串子元素,请改用 type: 'string' 声明一个显式的 children 字段。
Interactive.textSchema 控制排版样式;childrenSchema 控制内容。
子片段不包含在 baseSchema 中,也不会自动添加到自定义组件。
嵌套标记和计算出的子元素目前是只读的。
text-content 是 string 字段类型的已弃用别名。
有关所有选项,请参阅 InteractivitySchema。
将可重用组件注册为连接合成
将重要的场景和具有自己图层或动画的可重用组件注册为独立的合成。Studio 将这些称为连接合成:它们可以在自己的时间线中打开,同时与父视频共享相同的组件实现。
在组件内部定义所需的样式和加载字体,以便它在没有父级的情况下工作。请参阅父级独立性。
注册父级渲染的同一个导出组件引用。对于上面的示例,使用 component={Scene},而不是 SceneInner 或内联包装器。使用 wrapInSequence: true,则连接不需要额外的 <Sequence>。
import {Composition} from 'remotion';
import {Scene} from './Scene';
export const RemotionRoot = () => (
<Composition
id="Scene"
component={Scene}
width={1280}
height={720}
fps={30}
durationInFrames={30}
defaultProps={{
title: 'A new perspective',
}}
/>
);
选择一个唯一的 ID、有代表性的内联默认值以及适合预览组件的宽度、fps 和时长。注册默认值不会自动从父实例复制 prop。请参阅连接的合成以了解注册和提取详细信息。
直接将时间放在组件上
避免在已经接受时间 props 的单个组件周围放置单独的 <Sequence>。使用 wrapInSequence: true 创建的内置交互组件和自定义组件可以直接接收 name、from、durationInFrames、trimBefore、playbackRate 和 premountFor。
<Scene
name="Opening scene"
from={30}
durationInFrames={90}
premountFor={fps}
style={{opacity: 0.9}}
title="A new perspective"
/>
当移除冗余序列时,将其时间和名称移动到子组件。
保留任何现有的子组件时间;不要覆盖或双重应用偏移。
组件内部的 useCurrentFrame() 调用读取其本地帧,而调用点的样式表达式仍然使用调用者的帧。移动动画样式时保留这种区别。
当 <Sequence> 为多个兄弟组件提供共享时间、包装不处理时间的组件或提供子组件无法直接接受的尺寸时,保留它。托管根也接受 width 和 height 用于 useVideoConfig()。保留 <TransitionSeries.Sequence> 用于连续布局,无论是否有过渡;直接的 from props 不会取代这些行为。
为每个独立可编辑项提供其自己的 JSX 节点
Studio 编辑创建项目的 JSX 源节点。如果多个运行时项目来自同一个 JSX 节点,则它们共享一个源编辑目标。
对于每个应独立编辑的合成注册、剪辑、场景、层或序列,编写一个单独的 JSX 节点,并将可编辑 props 保持在该节点上。这适用于 <Composition>、<Still>、内置媒体组件、<Sequence>、<TransitionSeries.Sequence> 和自定义组件。
例如,像这样编写一个可编辑的时间线:
import { TransitionSeries } from "@remotion/transitions";
<TransitionSeries>
<TransitionSeries.Sequence name="Introduction" durationInFrames={90}>
<Introduction />
</TransitionSeries.Sequence>
<TransitionSeries.Sequence name="Demo" durationInFrames={150}>
<Demo />
</TransitionSeries.Sequence>
</TransitionSeries>
.map() 或其他程序化循环会从一个 JSX 源节点创建多个运行时项目。这对于有意作为单个模板控制的重复输出(例如可视化条或粒子)是合适的。当实例需要独立的 ID 或名称、props、元数据、时间、排序、在 Studio 中删除或复制时,则不适用。
使用 Interactive 使 HTML 元素具有交互性
每个 HTML 和 SVG 元素(除了 <Img>,它已经是交互式的),例如 <div>,都可以使用 Interactive 变为交互式:
<Interactive.Div
name="Greeting card"
style={{fontSize: 80, padding: 24}}
>
Hello
</Interactive.Div>
这允许在 Studio 中设置样式和关键帧。要明智,如果组件有许多元素,时间线可能会变得混乱。
优先使用内联文本
如果文本是固定的并且只使用一次,请直接将其写在交互元素内部,而不是将其提取到常量中。
// 👍 固定副本保持可编辑
<Interactive.Div name="Title">
Remotion Best Practices
</Interactive.Div>
仅在文本是动态或可重用时使用 prop 或变量。
为交互元素提供描述性名称
向元素添加 name prop 以使它们易于识别。
避免使用计算名称,硬编码它们。
<>
<Interactive.Div name="Hero title" style={{fontSize: 80}}>
Launch day
</Interactive.Div>
<Img name="Avatar" src="https://remotion.media/image.jpeg" />
<Video name="Background" src="https://remotion.media/video.mp4" />
<Interactive.Div name="Title">
Launch day
</Interactive.Div>
</>
将所有 CSS 样式保持内联
最好的方法是直接将普通对象传递给 style - 不引用常量,不进行对象展开,不进行数学运算。
<Interactive.Div
style={{
fontSize: 80,
color: 'red',
}}
>
Hello World!
</Interactive.Div>
const baseStyle = useMemo(() => {
return {
fontSize: 12 // ❌ 非内联样式不受支持
}
}, []);
<Interactive.Div
style={{
...baseStyle, // ❌ 展开不受支持
color: RED, // ❌ 引用常量不受支持
scale: frame * 10 // ❌ 数学运算不受支持
}}
>
Hello World!
</Interactive.Div>
使用 interpolate() 进行动画
在变化的属性上编写动画作为内联 interpolate() 调用。
输出范围、缓动、外推和 output 属性应使用硬编码值。
输入范围可能额外使用从 useVideoConfig() 直接解构的 durationInFrames、fps、width 和 height。支持诸如 durationInFrames 之类的裸标识符、与数字相乘(例如 2 * fps 或 fps * 2)以及减去数字(例如 durationInFrames - 1)之类的操作。
const {fps, durationInFrames} = useVideoConfig();
// 👍 内联值可以标准化和关键帧化
<Interactive.Div
name="Product card"
style={{
color: 'white',
fontSize: 80,
scale: interpolate(frame, [0, fps], [0, 1], {
easing: Easing.spring({damping: 200}),
output: 'perceptual-scale',
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
}),
rotate: interpolate(frame, [0, 1 * fps], ['0deg', '20deg'], {
easing: Easing.spring({damping: 200}),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
}),
translate: interpolate(
frame,
[durationInFrames - 30, durationInFrames],
['0px 0px', '0px 120px'],
{
easing: Easing.spring({damping: 200}),
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
}
)
}}
>
Awesome Product
</Interactive.Div>
interpolate() 的返回值也可以直接赋值给样式属性,或与静态值组合,例如 translate: (someInterpolation + ' scale(1.5)')。
const {fps} = useVideoConfig();
const scale = interpolate(frame, [0, fps], [0, 1], {
easing: Easing.bezier(0.17, 0.67, 0.83, 0.67),
output: 'perceptual-scale',
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// 👍 提取的 interpolation 仍然是交互式的,因为它是一个直接的变量引用
<Interactive.Div
name="Product card"
style={{
color: 'white',
fontSize: 80,
scale,
}}
>
Awesome Product
</Interactive.Div>
依赖对象、函数调用或复杂表达式的 Animated value 不会被 Studio 拾取。当 Studio 无法使组件交互时,它会将值显示为灰色,但仍然可以在源代码级别编辑它们。
如果动画表达式在 Studio 中无法识别,其值将变为灰色。组件本身仍然完全可用;用户只需能够识别这些值不可在 Studio UI 中编辑。
