remotion-bits

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Remotion Bits

Remotion Bits

Animation components and utilities for building Remotion videos. The library's most powerful feature is Scene3D — a camera-based 3D presentation system (like impress.js) that enables cinematic multi-section compositions with flying camera moves, step-aware element animations, and Transform3D position management.
When building any non-trivial composition, prefer Scene3D as your foundation. It handles camera movement, timing, element positioning, and responsive layout all in one system. Individual components (AnimatedText, Particles, etc.) work best as content placed inside Scene3D steps.
Prerequisites:
remotion
>= 4.0,
react
>= 18,
react-dom
>= 18
bash
undefined
用于构建Remotion视频的动画组件与工具集。该库最强大的功能是Scene3D——一个基于相机的3D展示系统(类似impress.js),可实现带有飞行动画镜头、步骤感知元素动画和Transform3D位置管理的电影级多段合成内容。
**在构建任何非简单合成内容时,优先选择Scene3D作为基础框架。**它一站式处理镜头移动、时间控制、元素定位和响应式布局。单个组件(如AnimatedText、Particles等)作为内容放置在Scene3D的步骤中使用效果最佳。
前置要求:
remotion
>= 4.0,
react
>= 18,
react-dom
>= 18
bash
undefined

Via npm

Via npm

npm install remotion-bits
npm install remotion-bits

Via jsrepo (copies source for customization)

Via jsrepo (copies source for customization)

npx jsrepo init https://unpkg.com/remotion-bits/registry.json npx jsrepo add animated-text particle-system scene-3d
undefined
npx jsrepo init https://unpkg.com/remotion-bits/registry.json npx jsrepo add animated-text particle-system scene-3d
undefined

Imports

导入方式

Everything is exported from
remotion-bits
:
tsx
import {
  // 3D Scene System (primary workflow)
  Scene3D, Step, Element3D, StepResponsive,
  Transform3D, Vector3,
  useScene3D, useCamera, useActiveStep,
  // Animation Components
  AnimatedText, AnimatedCounter, TypeWriter, CodeBlock,
  StaggeredMotion, GradientTransition, MatrixRain, ScrollingColumns,
  Particles, Spawner, Behavior,
  // Core
  useViewportRect,
  interpolate, Easing,
  interpolateColorKeyframes, interpolateGradientKeyframes,
  random, resolvePoint, createRect,
} from "remotion-bits";
所有功能均从
remotion-bits
导出:
tsx
import {
  // 3D场景系统(核心工作流)
  Scene3D, Step, Element3D, StepResponsive,
  Transform3D, Vector3,
  useScene3D, useCamera, useActiveStep,
  // 动画组件
  AnimatedText, AnimatedCounter, TypeWriter, CodeBlock,
  StaggeredMotion, GradientTransition, MatrixRain, ScrollingColumns,
  Particles, Spawner, Behavior,
  // 核心工具
  useViewportRect,
  interpolate, Easing,
  interpolateColorKeyframes, interpolateGradientKeyframes,
  random, resolvePoint, createRect,
} from "remotion-bits";

Core Concepts

核心概念

1. AnimatedValue

1. AnimatedValue

Most animation properties accept
AnimatedValue
: a static number OR an array of keyframes interpolated over the animation's duration.
tsx
opacity: 1                // Static
opacity: [0, 1]           // Animate 0→1
opacity: [0, 1, 0.5, 0]  // Multi-keyframe: 0→1→0.5→0 evenly spaced
scale: [0.8, 1]           // Scale from 80% to 100%
y: [30, 0]                // Slide up from 30px offset
Keyframes are evenly distributed across the duration. With 4 keyframes over 60 frames: frame 0→20→40→60.
大多数动画属性支持
AnimatedValue
:可以是静态数值,也可以是随动画时长插值的关键帧数组。
tsx
opacity: 1                // 静态值
opacity: [0, 1]           // 从0动画到1
opacity: [0, 1, 0.5, 0]  // 多关键帧:0→1→0.5→0,均匀分布
scale: [0.8, 1]           // 从80%缩放至100%
y: [30, 0]                // 从下方30px位置向上滑动
关键帧在动画时长内均匀分布。例如60帧动画包含4个关键帧时,会在第0、20、40、60帧触发关键帧变化。

2. Responsive Sizing with useViewportRect

2. 使用useViewportRect实现响应式尺寸

Never hardcode pixel values. Always use viewport-relative units:
tsx
const rect = useViewportRect();
// rect.width   — composition width (e.g. 1920)
// rect.height  — composition height (e.g. 1080)
// rect.vw      — 1% of width (19.2)
// rect.vh      — 1% of height (10.8)
// rect.vmin    — min(vw, vh) — USE THIS for most sizing
// rect.vmax    — max(vw, vh)
// rect.cx, cy  — center coordinates
const { vmin } = rect;
Use
vmin
for font sizes, element dimensions, spacing, and padding. This ensures compositions render identically at 1920×1080, 1080×1920, 3840×2160, etc.
**绝对不要硬编码像素值。**始终使用视口相对单位:
tsx
const rect = useViewportRect();
// rect.width   — 合成内容宽度(如1920)
// rect.height  — 合成内容高度(如1080)
// rect.vw      — 宽度的1%(19.2)
// rect.vh      — 高度的1%(10.8)
// rect.vmin    — vw和vh中的较小值 — 大多数尺寸设置优先使用这个
// rect.vmax    — vw和vh中的较大值
// rect.cx, cy  — 中心坐标
const { vmin } = rect;
使用
vmin
设置字体大小、元素尺寸、间距和内边距。这能确保合成内容在1920×1080、1080×1920、3840×2160等不同分辨率下渲染效果一致。

Understanding vmin at Common Resolutions

常见分辨率下的vmin理解

vmin
=
min(width, height) / 100
. Always compute what vmin equals in pixels for your composition size before choosing multipliers.
Composition SizeAspect Ratiovmin (px)vmin * 8vmin * 10vmin * 5
1920×108016:9 landscape10.886px108px54px
1080×19209:16 portrait10.886px108px54px
1080×10801:1 square10.886px108px54px
3840×216016:9 4K21.6173px216px108px
1280×72016:9 720p7.258px72px36px
vmin
=
min(width, height) / 100
在选择乘数前,务必先计算当前合成尺寸下vmin对应的像素值。
合成尺寸宽高比vmin(像素)vmin * 8vmin * 10vmin * 5
1920×108016:9 横屏10.886px108px54px
1080×19209:16 竖屏10.886px108px54px
1080×10801:1 正方形10.886px108px54px
3840×216016:9 4K21.6173px216px108px
1280×72016:9 720p7.258px72px36px

Font Size Reference (at 1920×1080, vmin = 10.8px)

字体大小参考(1920×1080分辨率下,vmin=10.8px)

Use these proven multipliers from production bits. Err on the side of LARGER, not smaller.
RoleMultiplierPixels at 1080pExample
Hero / main title
vmin * 10–15
108–162pxFull-screen headline
Section heading
vmin * 6–8
65–86pxScene3D step titles
Subheading
vmin * 4–5
43–54pxCard titles, counters
Body / card label
vmin * 2.5–3
27–32pxFeature labels, descriptions
Code / small text
vmin * 1.5–2
16–22pxCode blocks, captions
Fine print
vmin * 1–1.2
11–13pxCode font in dense blocks
Common mistake: using
vmin * 3
for headings.
At 1080p that's only 32px — fine for body text but too small for any heading. For prominent headings, start at
vmin * 8
minimum.
使用经过生产验证的乘数。优先选择更大的数值,而非更小。
用途乘数1080p下的像素值示例
主标题/大标题
vmin * 10–15
108–162px全屏标题
章节标题
vmin * 6–8
65–86pxScene3D步骤标题
副标题
vmin * 4–5
43–54px卡片标题、计数器
正文/卡片标签
vmin * 2.5–3
27–32px功能标签、描述文本
代码/小字
vmin * 1.5–2
16–22px代码块、说明文字
极小文字
vmin * 1–1.2
11–13px密集代码块中的字体
常见错误: 使用
vmin * 3
设置标题。在1080p下这仅为32px——适合正文但完全不适合标题。重要标题至少从
vmin * 8
开始设置。

Aspect Ratio Awareness

宽高比适配

The aspect ratio determines which dimension is the "min" for vmin:
  • Landscape (16:9, 1920×1080):
    vmin
    is based on height (1080/100 = 10.8). Horizontal space is abundant; vertical space is limited.
  • Portrait (9:16, 1080×1920):
    vmin
    is based on width (1080/100 = 10.8). Vertical space is abundant; horizontal space is limited.
  • Square (1:1, 1080×1080):
    vmin = vmax = vh = vw
    . All directions equal.
Layout implications:
  • In landscape, full-width text can be very long — consider
    width: vmin * 70
    or
    maxWidth
    constraints
  • In portrait, titles may need to wrap — use fewer words or smaller multipliers for width-constrained text
  • Use
    vmin
    for sizes that should scale uniformly regardless of orientation
  • Use
    vw
    /
    vh
    when sizing should follow a specific axis (e.g., full-width background:
    rect.width
    )
宽高比决定了vmin是基于宽度还是高度计算:
  • 横屏(16:9,1920×1080):
    vmin
    基于高度计算(1080/100=10.8)。水平空间充足,垂直空间有限。
  • 竖屏(9:16,1080×1920):
    vmin
    基于宽度计算(1080/100=10.8)。垂直空间充足,水平空间有限。
  • 正方形(1:1,1080×1080):
    vmin = vmax = vh = vw
    。所有方向空间一致。
布局注意事项:
  • 横屏模式下,全屏文本可能过长——考虑设置
    width: vmin * 70
    maxWidth
    限制
  • 竖屏模式下,标题可能需要换行——减少字数或使用更小的宽度乘数
  • 使用
    vmin
    设置需要随方向均匀缩放的尺寸
  • 当尺寸需要跟随特定轴时使用
    vw
    /
    vh
    (例如全屏背景:
    rect.width

Element Size Reference

元素尺寸参考

ElementMultiplierDescription
Card width
vmin * 50–70
Floating card, info panel
Card height
vmin * 25–40
Match content needs
Card border-radius
vmin * 1–2
Subtle rounding
Spacing/gap
vmin * 1–3
Between elements
Padding
vmin * 2–4
Inside containers
Particle size
vmin * 1–3
Individual particle dot
Icon size
vmin * 8–12
Decorative icons
元素乘数描述
卡片宽度
vmin * 50–70
悬浮卡片、信息面板
卡片高度
vmin * 25–40
根据内容需求调整
卡片圆角
vmin * 1–2
轻微圆角效果
元素间距
vmin * 1–3
元素之间的间隔
内边距
vmin * 2–4
容器内部填充
粒子大小
vmin * 1–3
单个粒子点
图标大小
vmin * 8–12
装饰性图标

3. Transition Props Pattern

3. 过渡属性模式

Components accept a
transition
prop with this shape:
tsx
transition={{
  // Timing
  duration: 30,           // Duration in frames
  delay: 10,              // Delay before start
  frames: [0, 60],        // Or explicit frame range (alternative to duration)
  easing: "easeOutCubic", // Easing curve

  // Transform (all accept AnimatedValue)
  x: [30, 0],             // translateX
  y: [30, 0],             // translateY
  z: [0, 100],            // translateZ (3D)
  scale: [0.8, 1],        // uniform scale
  scaleX, scaleY,         // axis scale
  rotate: [0, 360],       // 2D rotation (degrees)
  rotateX, rotateY, rotateZ, // 3D rotation (degrees)
  skew, skewX, skewY,     // skew (degrees)
  transform: Transform3D[], // 3D transform keyframes (see Transform3D section)

  // Visual (all accept AnimatedValue)
  opacity: [0, 1],
  blur: [10, 0],          // Gaussian blur in pixels
  borderRadius: [0, 20],
  color: ["#ff0000", "#00ff00"],           // CSS color interpolation
  backgroundColor: ["#000", "#fff"],
}}
组件支持
transition
属性,格式如下:
tsx
transition={{
  // 时间设置
  duration: 30,           // 动画时长(帧)
  delay: 10,              // 延迟开始时间(帧)
  frames: [0, 60],        // 或显式设置帧范围(替代duration)
  easing: "easeOutCubic", // 缓动曲线

  // 变换属性(均支持AnimatedValue)
  x: [30, 0],             // X轴位移
  y: [30, 0],             // Y轴位移
  z: [0, 100],            // Z轴位移(3D)
  scale: [0.8, 1],        // 均匀缩放
  scaleX, scaleY,         // 单轴缩放
  rotate: [0, 360],       // 2D旋转(角度)
  rotateX, rotateY, rotateZ, // 3D旋转(角度)
  skew, skewX, skewY,     // 倾斜(角度)
  transform: Transform3D[], // 3D变换关键帧(参考Transform3D章节)

  // 视觉属性(均支持AnimatedValue)
  opacity: [0, 1],
  blur: [10, 0],          // 高斯模糊(像素)
  borderRadius: [0, 20],
  color: ["#ff0000", "#00ff00"],           // CSS颜色插值
  backgroundColor: ["#000", "#fff"],
}}

4. Easing Functions

4. 缓动函数

tsx
type EasingName =
  | "linear"
  | "easeIn" | "easeOut" | "easeInOut"
  | "easeInQuad" | "easeOutQuad" | "easeInOutQuad"
  | "easeInCubic" | "easeOutCubic" | "easeInOutCubic"
  | "easeInSine" | "easeOutSine" | "easeInOutSine"
  | "easeInQuart" | "easeOutQuart" | "easeInOutQuart";
Most common:
"easeOutCubic"
for entries,
"easeInOutCubic"
for camera moves.
tsx
type EasingName =
  | "linear"
  | "easeIn" | "easeOut" | "easeInOut"
  | "easeInQuad" | "easeOutQuad" | "easeInOutQuad"
  | "easeInCubic" | "easeOutCubic" | "easeInOutCubic"
  | "easeInSine" | "easeOutSine" | "easeInOutSine"
  | "easeInQuart" | "easeOutQuart" | "easeInOutQuart";
最常用的:元素入场使用
"easeOutCubic"
,镜头移动使用
"easeInOutCubic"

5. Layout & Display Configuration

5. 布局与显示配置

CRITICAL: Bits are pre-wrapped with a layout container when displayed in the docs. The wrapper (
withShowcaseFill
) provides an
AbsoluteFill
with default styling. When building standalone compositions, you must provide this layout yourself.
重要提示: 文档中展示的组件会自动包裹一层布局容器(
withShowcaseFill
),该容器提供了
AbsoluteFill
和默认样式。在构建独立合成内容时,你必须自行提供布局容器。

The Layout Wrapper

布局容器说明

Bits displayed in docs are automatically wrapped with:
tsx
<AbsoluteFill style={{
  backgroundColor: 'var(--color-background-dark)',  // #100f0f
  fontSize: `${config.width * 0.05}px`,             // 5% of width
  fontWeight: 700,
  color: 'var(--color-primary-hover)',               // #fcc192
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
}}>
  <Component />
</AbsoluteFill>
This means simple bits like
<AnimatedText>Hello</AnimatedText>
work because the wrapper centers them and provides background/color.
文档中展示的组件会自动被以下容器包裹:
tsx
<AbsoluteFill style={{
  backgroundColor: 'var(--color-background-dark)',  // #100f0f
  fontSize: `${config.width * 0.05}px`,             // 宽度的5%
  fontWeight: 700,
  color: 'var(--color-primary-hover)',               // #fcc192
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
}}>
  <Component />
</AbsoluteFill>
这意味着像
<AnimatedText>Hello</AnimatedText>
这样简单的组件能直接工作,因为容器已经处理了居中、背景和颜色。

Building Standalone Compositions

构建独立合成内容

When creating compositions for direct use (not displayed through the docs wrapper), you MUST provide your own layout:
tsx
import { AbsoluteFill } from 'remotion';

export const MyComposition: React.FC = () => {
  const rect = useViewportRect();
  const { vmin } = rect;

  return (
    <AbsoluteFill style={{
      backgroundColor: '#100f0f',
      color: '#fcc192',
      fontFamily: 'system-ui, sans-serif',
    }}>
      {/* Your content here */}
      <Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
        {/* Steps... */}
      </Scene3D>
    </AbsoluteFill>
  );
};
当创建直接使用的合成内容(而非通过文档容器展示)时,必须自行提供布局容器
tsx
import { AbsoluteFill } from 'remotion';

export const MyComposition: React.FC = () => {
  const rect = useViewportRect();
  const { vmin } = rect;

  return (
    <AbsoluteFill style={{
      backgroundColor: '#100f0f',
      color: '#fcc192',
      fontFamily: 'system-ui, sans-serif',
    }}>
      {/* 你的内容 */}
      <Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
        {/* 步骤... */}
      </Scene3D>
    </AbsoluteFill>
  );
};

CSS Variables (Theming)

CSS变量(主题)

Bits use CSS variables for consistent theming. These are available when rendering within the docs but must be defined or replaced with literal values in standalone projects:
VariableDefault ValueUsage
--color-primary
#ec8b49
Accent orange
--color-primary-hover
#fcc192
Light orange / text color
--color-background-dark
#100f0f
Dark background
--color-surface-dark
#1c1b1a
Surface/card background
--color-surface-light
#343331
Lighter surface
--color-border-dark
#100f0f
Dark borders
--color-border-light
#1c1b1a
Light borders
In standalone projects: Replace
var(--color-*)
with literal hex values, or define these variables in your HTML/CSS.
组件使用CSS变量实现一致的主题。在文档中渲染时这些变量会自动生效,但在独立项目中必须定义这些变量或替换为具体值
变量默认值用途
--color-primary
#ec8b49
主色调橙色
--color-primary-hover
#fcc192
浅橙色/文本颜色
--color-background-dark
#100f0f
深色背景
--color-surface-dark
#1c1b1a
卡片/表面背景
--color-surface-light
#343331
浅色表面
--color-border-dark
#100f0f
深色边框
--color-border-light
#1c1b1a
浅色边框
在独立项目中:
var(--color-*)
替换为具体的十六进制颜色值,或在HTML/CSS中定义这些变量。

Common Layout Mistakes

常见布局错误

  1. Missing background: Content renders on transparent/white background. Always set
    backgroundColor
    on the outermost container.
  2. Missing AbsoluteFill: Scene3D and full-viewport compositions need
    <AbsoluteFill>
    from remotion as their root.
  3. Using CSS vars without defining them:
    var(--color-primary)
    resolves to nothing in a bare Remotion project. Use literal colors.
  4. Hardcoded pixel sizes: Use
    vmin
    -based sizing from
    useViewportRect()
    instead.
  5. Missing font settings: Set
    fontSize
    ,
    fontWeight
    ,
    fontFamily
    , and
    color
    explicitly — there are no inherited defaults in Remotion.
  1. 缺少背景: 内容在透明/白色背景上渲染。始终为最外层容器设置
    backgroundColor
  2. 缺少AbsoluteFill: Scene3D和全屏合成内容需要使用Remotion提供的
    <AbsoluteFill>
    作为根容器。
  3. 未定义CSS变量: 在纯Remotion项目中,
    var(--color-primary)
    不会生效。使用具体颜色值。
  4. 硬编码像素尺寸: 使用
    useViewportRect()
    提供的
    vmin
    相关尺寸替代。
  5. 缺少字体设置: 显式设置
    fontSize
    fontWeight
    fontFamily
    color
    ——Remotion没有继承默认样式。

Three Layout Patterns

三种布局模式

Simple (centered content, uses wrapper defaults):
tsx
// Relies on the outer wrapper for background, centering, font
export const Component = () => (
  <AnimatedText transition={{ opacity: [0, 1] }}>Hello</AnimatedText>
);
Self-contained (own background and layout):
tsx
export const Component = () => (
  <div style={{
    width: '100%', height: '100%',
    backgroundColor: '#09090b',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
  }}>
    <StaggeredMotion transition={{ scale: [0, 1], opacity: [0, 1] }}>
      {items}
    </StaggeredMotion>
  </div>
);
Full 3D scene (recommended for complex work):
tsx
export const Component = () => {
  const rect = useViewportRect();
  return (
    <AbsoluteFill style={{
      background: '#100f0f',
      color: '#fcc192',
    }}>
      <Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
        <Step id="intro" {...positions.base.toProps()} />
        {/* ... */}
      </Scene3D>
    </AbsoluteFill>
  );
};

简单模式(居中内容,依赖容器默认样式):
tsx
// 依赖外层容器提供背景、居中、字体样式
export const Component = () => (
  <AnimatedText transition={{ opacity: [0, 1] }}>Hello</AnimatedText>
);
自包含模式(自带背景和布局):
tsx
export const Component = () => (
  <div style={{
    width: '100%', height: '100%',
    backgroundColor: '#09090b',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
  }}>
    <StaggeredMotion transition={{ scale: [0, 1], opacity: [0, 1] }}>
      {items}
    </StaggeredMotion>
  </div>
);
完整3D场景模式(复杂项目推荐):
tsx
export const Component = () => {
  const rect = useViewportRect();
  return (
    <AbsoluteFill style={{
      background: '#100f0f',
      color: '#fcc192',
    }}>
      <Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
        <Step id="intro" {...positions.base.toProps()} />
        {/* ... */}
      </Scene3D>
    </AbsoluteFill>
  );
};

Transform3D (Critical for Complex Scenes)

Transform3D(复杂场景必备)

Transform3D
represents a 3D transformation (position + rotation + scale) using Three.js internals. It is immutable by convention — every method returns a new instance.
tsx
import { Transform3D, Vector3 } from "remotion-bits";
Transform3D
使用Three.js内部机制表示3D变换(位置+旋转+缩放)。按照约定它是不可变的——所有方法都会返回新的实例。
tsx
import { Transform3D, Vector3 } from "remotion-bits";

Creating Transforms

创建变换对象

tsx
const base = Transform3D.identity(); // Origin: position(0,0,0), rotation(0,0,0), scale(1,1,1)
tsx
const base = Transform3D.identity(); // 初始状态:位置(0,0,0),旋转(0,0,0),缩放(1,1,1)

Chaining Transforms

链式调用变换

Every method returns a new Transform3D. Chain freely:
tsx
const cardPosition = base
  .translate(vmin * 50, vmin * -20, 0)  // Move right and up
  .rotateY(-15)                          // Rotate around Y axis (degrees)
  .scaleBy(1.5);                         // Scale uniformly by 1.5x
每个方法都会返回新的Transform3D对象,可以自由链式调用:
tsx
const cardPosition = base
  .translate(vmin * 50, vmin * -20, 0)  // 向右向上移动
  .rotateY(-15)                          // 绕Y轴旋转(角度)
  .scaleBy(1.5);                         // 均匀缩放1.5倍

Available Methods

可用方法

tsx
// Position
transform.translate(x, y, z)           // Add to position
transform.translate(vector3)            // Add Vector3 to position

// Rotation (angles in DEGREES)
transform.rotateX(degrees)              // Rotate around X axis
transform.rotateY(degrees)              // Rotate around Y axis
transform.rotateZ(degrees)              // Rotate around Z axis
transform.rotateAround(origin, axis, degrees) // Rotate around arbitrary point+axis

// Scale
transform.scaleBy(uniform)             // Scale all axes equally
transform.scaleBy(sx, sy, sz)          // Scale per-axis

// Composition
transform.multiply(other)              // Matrix multiplication
transform.inverse()                    // Invert transform
transform.lerp(target, alpha)          // Linear interpolation (0-1)
transform.clone()                      // Deep copy

// Randomization (deterministic via seed)
transform.randomTranslate([minX, maxX], [minY, maxY], [minZ, maxZ], seed)
transform.randomRotateX([minDeg, maxDeg], seed)
transform.randomRotateY([minDeg, maxDeg], seed)
transform.randomRotateZ([minDeg, maxDeg], seed)

// Conversion
transform.toProps()                    // → { x, y, z, rotateX, rotateY, rotateZ, scaleX, scaleY, scaleZ, rotateOrder }
transform.toCSSMatrix3D()              // → "matrix3d(...)" CSS string
transform.toMatrix4()                  // → Three.js Matrix4
tsx
// 位置
transform.translate(x, y, z)           // 添加位置偏移
transform.translate(vector3)            // 添加Vector3位置偏移

// 旋转(角度为度数)
transform.rotateX(degrees)              // 绕X轴旋转
transform.rotateY(degrees)              // 绕Y轴旋转
transform.rotateZ(degrees)              // 绕Z轴旋转
transform.rotateAround(origin, axis, degrees) // 绕任意点和轴旋转

// 缩放
transform.scaleBy(uniform)             // 所有轴均匀缩放
transform.scaleBy(sx, sy, sz)          // 单轴独立缩放

// 变换组合
transform.multiply(other)              // 矩阵乘法
transform.inverse()                    // 反转变换
transform.lerp(target, alpha)          // 线性插值(0-1)
transform.clone()                      // 深拷贝

// 随机化(通过种子保证确定性)
transform.randomTranslate([minX, maxX], [minY, maxY], [minZ, maxZ], seed)
transform.randomRotateX([minDeg, maxDeg], seed)
transform.randomRotateY([minDeg, maxDeg], seed)
transform.randomRotateZ([minDeg, maxDeg], seed)

// 转换
transform.toProps()                    // → { x, y, z, rotateX, rotateY, rotateZ, scaleX, scaleY, scaleZ, rotateOrder }
transform.toCSSMatrix3D()              // → "matrix3d(...)" CSS字符串
transform.toMatrix4()                  // → Three.js Matrix4对象

Using Transform3D with Step/Element3D

结合Step/Element3D使用Transform3D

The
.toProps()
method converts a Transform3D to props that Step and Element3D accept:
tsx
const position = base.translate(vmin * 50, 0, 0).rotateY(-15);

// Spread directly into Step or Element3D
<Step id="my-step" {...position.toProps()} />
This is equivalent to manually specifying
x={...} y={...} z={...} rotateX={...} rotateY={...} rotateZ={...}
.
.toProps()
方法可以将Transform3D转换为Step和Element3D可接受的属性:
tsx
const position = base.translate(vmin * 50, 0, 0).rotateY(-15);

// 直接展开到Step或Element3D中
<Step id="my-step" {...position.toProps()} />
这等同于手动设置
x={...} y={...} z={...} rotateX={...} rotateY={...} rotateZ={...}

Using Transform3D as Keyframes

将Transform3D作为关键帧

Pass
Transform3D[]
arrays as the
transform
property for smooth 3D interpolation between positions:
tsx
const start = base.translate(0, vmin * 20, 0);
const end = base.translate(0, 0, 0);

<Element3D
  centered
  transition={{
    duration: 35,
    transform: [start, end],  // Interpolates position, rotation, scale via slerp
    opacity: [0, 1],
    easing: "easeInOutCubic",
  }}
>
  <div>Animates from start to end</div>
</Element3D>
Transform keyframes use quaternion slerp for rotation (no gimbal lock) and linear interpolation for position and scale.
传递
Transform3D[]
数组作为
transform
属性,可实现位置之间的平滑3D插值:
tsx
const start = base.translate(0, vmin * 20, 0);
const end = base.translate(0, 0, 0);

<Element3D
  centered
  transition={{
    duration: 35,
    transform: [start, end],  // 通过球面线性插值(slerp)实现位置、旋转、缩放的平滑过渡
    opacity: [0, 1],
    easing: "easeInOutCubic",
  }}
>
  <div>从起始位置动画到结束位置</div>
</Element3D>
变换关键帧使用四元数球面线性插值(slerp)处理旋转(避免万向锁问题),使用线性插值处理位置和缩放。

Vector3

Vector3

Three.js
Vector3
is re-exported for position math:
tsx
const offset = new Vector3(0, -vmin * 2, 0);
const farOffset = offset.clone().multiplyScalar(4.0); // Scale the offset
const combined = offset.clone().add(new Vector3(vmin * 5, 0, 0));

重新导出了Three.js的
Vector3
用于位置计算:
tsx
const offset = new Vector3(0, -vmin * 2, 0);
const farOffset = offset.clone().multiplyScalar(4.0); // 缩放偏移量
const combined = offset.clone().add(new Vector3(vmin * 5, 0, 0));

Scene3D System (3D Presentations)

Scene3D系统(3D展示)

Scene3D creates camera-based 3D presentations (like impress.js). The camera flies between Steps; content is placed in 3D space.
Scene3D用于创建基于相机的3D展示内容(类似impress.js)。相机会在不同Step之间飞行,内容放置在3D空间中。

Architecture

架构

Scene3D (perspective, timing)
├── Step (camera target 1) — children visible during this step
├── Step (camera target 2) — children visible during this step
├── ...
├── StepResponsive — element that animates differently per step
├── StepResponsive — another step-aware element
└── (any other children — always rendered)
Scene3D(透视、时间设置)
├── Step(相机目标1)—— 该步骤激活时显示子内容
├── Step(相机目标2)—— 该步骤激活时显示子内容
├── ...
├── StepResponsive —— 随步骤变化的动画元素
├── StepResponsive —— 另一个步骤感知元素
└──(其他子元素——始终显示)

Scene3D Container

Scene3D容器

tsx
<Scene3D
  perspective={1000}            // CSS perspective in px (depth effect)
  stepDuration={60}             // Default frames per step
  transitionDuration={60}       // Frames for camera transitions between steps
  easing="easeInOutCubic"       // Camera transition easing
>
  {/* Steps and content */}
</Scene3D>
tsx
<Scene3D
  perspective={1000}            // CSS透视值(像素,控制景深效果)
  stepDuration={60}             // 每个步骤的默认时长(帧)
  transitionDuration={60}       // 步骤间相机过渡的时长(帧)
  easing="easeInOutCubic"       // 相机过渡的缓动曲线
>
  {/* 步骤和内容 */}
</Scene3D>

Steps (Camera Targets)

Step(相机目标)

Steps define where the camera flies to. They execute sequentially. Content inside a Step is visible when that step is active.
tsx
<Step
  id="intro"                   // Unique identifier (used by StepResponsive)
  {...position.toProps()}       // Camera target position/rotation/scale
  duration={120}               // Override stepDuration for this step (optional)
  transition={{                // Animate children on step ENTRY (optional)
    opacity: [0, 1],
    blur: [10, 0],
    duration: 20,
  }}
  exitTransition={{            // Animate children on step EXIT (optional)
    opacity: [1, 0],
    blur: [0, 10],
  }}
>
  {/* Content shown during this step */}
  <FloatingCard>...</FloatingCard>
</Step>
Step timing model:
  • Steps play sequentially: step1 → transition → step2 → transition → step3
  • stepDuration
    (on Scene3D): default duration each step is active
  • duration
    (on Step): override for specific step
  • transitionDuration
    (on Scene3D): how long the camera takes to move between steps
  • Total composition should be ≥ sum of all step durations
Step定义了相机要飞往的位置。它们按顺序执行。Step内部的内容仅在该步骤激活时可见。
tsx
<Step
  id="intro"                   // 唯一标识符(供StepResponsive使用)
  {...position.toProps()}       // 相机目标的位置/旋转/缩放属性
  duration={120}               // 覆盖该步骤的默认时长(可选)
  transition={{                // 步骤激活时子元素的入场动画(可选)
    opacity: [0, 1],
    blur: [10, 0],
    duration: 20,
  }}
  exitTransition={{            // 步骤结束时子元素的退场动画(可选)
    opacity: [1, 0],
    blur: [0, 10],
  }}
>
  {/* 该步骤显示的内容 */}
  <FloatingCard>...</FloatingCard>
</Step>
Step时间模型:
  • Step按顺序执行:step1 → 过渡 → step2 → 过渡 → step3
  • stepDuration
    (Scene3D上的属性):每个步骤的默认激活时长
  • duration
    (Step上的属性):覆盖特定步骤的时长
  • transitionDuration
    (Scene3D上的属性):相机在步骤间移动的时长
  • 合成内容总时长应≥所有步骤时长之和

Element3D (3D Positioned Content)

Element3D(3D定位内容)

Places content at a specific 3D position, independent of camera:
tsx
<Element3D
  centered                     // Center-align the element (transform-origin: center)
  x={vmin * 50} y={0} z={0}   // Position in 3D space
  style={{ width: vmin * 60 }}
  transition={{                // Animate on mount (optional)
    delay: 20,
    opacity: [0, 1],
    duration: 35,
    transform: [startTransform, endTransform], // 3D keyframes
    easing: "easeInOutCubic",
  }}
>
  <div>Content positioned in 3D space</div>
</Element3D>
将内容放置在3D空间的特定位置,与相机无关:
tsx
<Element3D
  centered                     // 元素居中对齐(transform-origin: center)
  x={vmin * 50} y={0} z={0}   // 3D空间位置
  style={{ width: vmin * 60 }}
  transition={{                // 挂载时的动画(可选)
    delay: 20,
    opacity: [0, 1],
    duration: 35,
    transform: [startTransform, endTransform], // 3D关键帧
    easing: "easeInOutCubic",
  }}
>
  <div>放置在3D空间中的内容</div>
</Element3D>

StepResponsive (Step-Aware Animations)

StepResponsive(步骤感知动画)

The key to complex scenes. Elements define how they should look/position at each step, and animate between states when the camera moves:
tsx
<StepResponsive
  centered                     // Center the child
  style={{ position: 'absolute', fontSize }}
  steps={{
    // Key = step ID, Value = properties at that step
    'intro': {
      transform: [base, shiftedPosition],  // Transform3D keyframes
      opacity: [0, 1],
    },
    'elements': {
      transform: [elementPosition],        // Hold at this position
    },
    'outro': {
      transform: [outroStart, outroEnd],
      opacity: [1, 1, 1, 0],              // Hold visible, then fade
      duration: "step",                    // Match step duration
      easing: "easeInOutCubic",
    },
  }}
>
  <h1>Title That Follows Camera</h1>
</StepResponsive>
StepResponsive key behaviors:
  • Properties accumulate/inherit: if step "elements" doesn't set opacity, it keeps the value from the last step that set it
  • Arrays flatten to their final value when moving to the next step (no re-animation of past keyframes)
  • transform
    accepts
    Transform3D[]
    arrays — the primary way to position elements in 3D
  • duration: "step"
    makes the animation last the entire step duration
  • You can map the same props to multiple step IDs to hold position across steps
Mapping same props to multiple steps (common pattern):
tsx
const mapToAllElementSteps = (props) => ({
  'elements': props,
  'element-particles': props,
  'element-text': props,
  'element-code': props,
});

<StepResponsive
  steps={{
    'intro': { transform: [introPosition] },
    ...mapToAllElementSteps({ transform: [elementPosition] }),
    'outro': { transform: [outroPosition] },
  }}
>
  <h1>Elements</h1>
</StepResponsive>

这是构建复杂场景的关键。元素可以定义在每个步骤中的样式/位置,相机动画时元素会自动在不同状态间过渡:
tsx
<StepResponsive
  centered                     // 子元素居中
  style={{ position: 'absolute', fontSize }}
  steps={{
    // 键=步骤ID,值=该步骤的属性
    'intro': {
      transform: [base, shiftedPosition],  // Transform3D关键帧
      opacity: [0, 1],
    },
    'elements': {
      transform: [elementPosition],        // 保持该位置
    },
    'outro': {
      transform: [outroStart, outroEnd],
      opacity: [1, 1, 1, 0],              // 保持可见,然后淡出
      duration: "step",                    // 动画时长等于步骤时长
      easing: "easeInOutCubic",
    },
  }}
>
  <h1>跟随相机的标题</h1>
</StepResponsive>
StepResponsive核心特性:
  • 属性会累积/继承:如果步骤"elements"没有设置opacity,会沿用最后一个设置过opacity的步骤的值
  • 数组在切换到下一个步骤时会取最终值(不会重新播放之前的关键帧动画)
  • transform
    接受
    Transform3D[]
    数组——这是3D空间中定位元素的主要方式
  • duration: "step"
    让动画时长等于整个步骤的时长
  • 可以将相同属性映射到多个步骤ID,实现跨步骤保持位置
将相同属性映射到多个步骤(常见模式):
tsx
const mapToAllElementSteps = (props) => ({
  'elements': props,
  'element-particles': props,
  'element-text': props,
  'element-code': props,
});

<StepResponsive
  steps={{
    'intro': { transform: [introPosition] },
    ...mapToAllElementSteps({ transform: [elementPosition] }),
    'outro': { transform: [outroPosition] },
  }}
>
  <h1>元素</h1>
</StepResponsive>

Building Complex Scenes (Architecture Guide)

构建复杂场景(架构指南)

This is the recommended approach for any non-trivial composition. Scene3D provides camera management, step-based timing, 3D element positioning, and step-responsive animations — eliminating the need to manually manage
useCurrentFrame()
,
<Sequence>
, or CSS transforms.\n\n### Step 1: Plan the Scene Structure
Decide on the major sections (acts) and what the camera shows in each:
intro → elements → element-particles → element-text → ... → transitions → scenes → outro
Each section = one Step. Steps execute sequentially.
这是构建非简单合成内容的推荐方法。 Scene3D提供了相机管理、基于步骤的时间控制、3D元素定位和步骤感知动画——无需手动管理
useCurrentFrame()
<Sequence>
或CSS变换。

Step 2: Pre-compute All Positions

步骤1:规划场景结构

Use
useMemo
to build a position tree. This is THE critical architectural pattern:
tsx
const positions = useMemo(() => {
  const { vmin } = rect;
  const base = Transform3D.identity();

  // Define base positions for each scene section
  const elementsBase = base.translate(0, -vmin * 120, 0).rotateX(15);
  const transitionsBase = base.translate(vmin * 200, vmin * 50, 0).rotateY(-15);
  const scenesBase = base.translate(-vmin * 120, vmin * 70, 0).rotateY(15);

  // Derive sub-positions from bases
  const cardW = vmin * 70;
  const particlesCard = elementsBase.translate(-cardW, -vmin * 40, 0).rotateY(15);
  const textCard = elementsBase.translate(0, -vmin * 50, vmin * 10).rotateX(10);

  return {
    base,
    elements: {
      base: elementsBase,
      cards: { particles: particlesCard, text: textCard },
    },
    transitions: { base: transitionsBase },
    scenes: { base: scenesBase },
  };
}, [rect.width, rect.height]); // Re-compute on resize
Why pre-compute positions?
  • Separation of layout from rendering
  • Positions can be derived from each other (hierarchy)
  • Easy to adjust entire sections by changing the base
  • StepResponsive needs position references, not inline calculations
确定主要章节(段落)以及每个章节中相机要展示的内容:
intro → elements → element-particles → element-text → ... → transitions → scenes → outro
每个章节对应一个Step,Step按顺序执行。

Step 3: Define Steps

步骤2:预计算所有位置

tsx
<Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
  <Step id="intro" {...positions.base.toProps()} />
  <Step id="elements" {...positions.elements.base.toProps()} />
  <Step id="element-particles"
    {...positions.elements.cards.particles.toProps()}
    transition={{ opacity: [0, 1], blur: [10, 0] }}
  >
    <FloatingCard><ParticleDemo /></FloatingCard>
  </Step>
  <Step id="transitions" duration={120} {...positions.transitions.base.toProps()} />
  <Step id="scenes" duration={120} {...positions.scenes.base.toProps()} />
  <Step id="outro" {...positions.base.toProps()} duration={120} />
</Scene3D>
使用
useMemo
构建位置树。这是至关重要的架构模式:
tsx
const positions = useMemo(() => {
  const { vmin } = rect;
  const base = Transform3D.identity();

  // 定义每个场景章节的基础位置
  const elementsBase = base.translate(0, -vmin * 120, 0).rotateX(15);
  const transitionsBase = base.translate(vmin * 200, vmin * 50, 0).rotateY(-15);
  const scenesBase = base.translate(-vmin * 120, vmin * 70, 0).rotateY(15);

  // 从基础位置派生子位置
  const cardW = vmin * 70;
  const particlesCard = elementsBase.translate(-cardW, -vmin * 40, 0).rotateY(15);
  const textCard = elementsBase.translate(0, -vmin * 50, vmin * 10).rotateX(10);

  return {
    base,
    elements: {
      base: elementsBase,
      cards: { particles: particlesCard, text: textCard },
    },
    transitions: { base: transitionsBase },
    scenes: { base: scenesBase },
  };
}, [rect.width, rect.height]); // 尺寸变化时重新计算
为什么要预计算位置?
  • 布局与渲染分离
  • 位置可以通过层级关系派生
  • 只需修改基础位置即可调整整个章节的布局
  • StepResponsive需要位置引用,而非内联计算

Step 4: Add Step-Responsive Elements

步骤3:定义Step

Titles, icons, and persistent elements that move with the camera:
tsx
<StepResponsive
  centered
  style={{ fontSize: vmin * 10, position: 'absolute' }}
  steps={{
    'intro': { transform: [positions.base, positions.base.translate(vmin * 7, 0, 0)] },
    'elements': { transform: [positions.elements.base.translate(vmin * 7, 0, 0)] },
    'outro': {
      transform: [positions.base.translate(vmin * 7, 0, 0), positions.base],
      duration: "step",
      easing: "easeInOutCubic",
    },
  }}
>
  <h1>Remotion Bits</h1>
</StepResponsive>
tsx
<Scene3D perspective={1000} stepDuration={60} transitionDuration={60}>
  <Step id="intro" {...positions.base.toProps()} />
  <Step id="elements" {...positions.elements.base.toProps()} />
  <Step id="element-particles"
    {...positions.elements.cards.particles.toProps()}
    transition={{ opacity: [0, 1], blur: [10, 0] }}
  >
    <FloatingCard><ParticleDemo /></FloatingCard>
  </Step>
  <Step id="transitions" duration={120} {...positions.transitions.base.toProps()} />
  <Step id="scenes" duration={120} {...positions.scenes.base.toProps()} />
  <Step id="outro" {...positions.base.toProps()} duration={120} />
</Scene3D>

Step 5: Content Inside Steps

步骤4:添加Step-Responsive元素

Steps can contain rich content — cards, particles, code blocks, counters:
tsx
<Step id="element-particles" {...cardPos.toProps()}
  transition={{ opacity: [0, 1], blur: [10, 0] }}>
  <FloatingCard>
    <Particles style={{ position: 'absolute', inset: 0, opacity: 0.6 }}>
      <Spawner rate={1} max={200} lifespan={80}
        velocity={{ x: 0, y: -0.6, varianceX: 0.4, varianceY: 0.2 }}
        area={{ width: rect.width, height: rect.height }}>
        <div style={{ width: vmin * 2, height: vmin * 2, borderRadius: '50%',
          background: 'var(--color-primary)' }} />
      </Spawner>
      <Behavior drag={0.96}
        wiggle={{ magnitude: 0.6, frequency: 0.25 }}
        opacity={[1, 0]}
        scale={{ start: 1, end: 0.4 }} />
    </Particles>
    <span style={{ position: 'relative', zIndex: 1, fontWeight: 'bold',
      fontSize: vmin * 3, fontFamily: 'monospace' }}>Particles</span>
  </FloatingCard>
</Step>

添加标题、图标和其他随相机移动的持久元素:
tsx
<StepResponsive
  centered
  style={{ fontSize: vmin * 10, position: 'absolute' }}
  steps={{
    'intro': { transform: [positions.base, positions.base.translate(vmin * 7, 0, 0)] },
    'elements': { transform: [positions.elements.base.translate(vmin * 7, 0, 0)] },
    'outro': {
      transform: [positions.base.translate(vmin * 7, 0, 0), positions.base],
      duration: "step",
      easing: "easeInOutCubic",
    },
  }}
>
  <h1>Remotion Bits</h1>
</StepResponsive>

Components Quick Reference

步骤5:Step内部的内容

AnimatedText

tsx
<AnimatedText
  style={{ fontSize: vmin * 5, fontWeight: 'bold' }}
  transition={{
    split: "character",           // "none" | "word" | "character" | "line" | custom separator
    splitStagger: 2,              // Frames between each unit
    opacity: [0, 1],
    y: [15, 0],
    blur: [2, 0],
    duration: 20,
    delay: 10,
    easing: "easeOutCubic",
    // Cycling text:
    cycle: { texts: ["Build", "Create", "Ship"], itemDuration: 40 },
    // Glitch effect:
    glitch: [0.6, 0],            // Glitch intensity AnimatedValue
  }}
>
  Hello World
</AnimatedText>
Step可以包含丰富的内容——卡片、粒子、代码块、计数器等:
tsx
<Step id="element-particles" {...cardPos.toProps()}
  transition={{ opacity: [0, 1], blur: [10, 0] }}>
  <FloatingCard>
    <Particles style={{ position: 'absolute', inset: 0, opacity: 0.6 }}>
      <Spawner rate={1} max={200} lifespan={80}
        velocity={{ x: 0, y: -0.6, varianceX: 0.4, varianceY: 0.2 }}
        area={{ width: rect.width, height: rect.height }}>
        <div style={{ width: vmin * 2, height: vmin * 2, borderRadius: '50%',
          background: 'var(--color-primary)' }} />
      </Spawner>
      <Behavior drag={0.96}
        wiggle={{ magnitude: 0.6, frequency: 0.25 }}
        opacity={[1, 0]}
        scale={{ start: 1, end: 0.4 }} />
    </Particles>
    <span style={{ position: 'relative', zIndex: 1, fontWeight: 'bold',
      fontSize: vmin * 3, fontFamily: 'monospace' }}>Particles</span>
  </FloatingCard>
</Step>

AnimatedCounter

组件快速参考

AnimatedText

tsx
<AnimatedCounter
  transition={{
    values: [0, 1000],            // Number keyframes
    color: ['#ff0000', '#00ff00'], // Color transition
    scale: [0.8, 1],
    duration: 30,
    delay: 10,
  }}
  prefix="$"
  postfix="+"
  toFixed={0}
  style={{ fontSize: vmin * 4, fontWeight: 'bold' }}
/>
tsx
<AnimatedText
  style={{ fontSize: vmin * 5, fontWeight: 'bold' }}
  transition={{
    split: "character",           // "none" | "word" | "character" | "line" | 自定义分隔符
    splitStagger: 2,              // 每个单元的动画延迟(帧)
    opacity: [0, 1],
    y: [15, 0],
    blur: [2, 0],
    duration: 20,
    delay: 10,
    easing: "easeOutCubic",
    // 循环文本:
    cycle: { texts: ["Build", "Create", "Ship"], itemDuration: 40 },
    // 故障效果:
    glitch: [0.6, 0],            // 故障强度AnimatedValue
  }}
>
  Hello World
</AnimatedText>

TypeWriter

AnimatedCounter

tsx
<TypeWriter
  text="import { TypeWriter } from 'remotion-bits';"
  typeSpeed={2}        // Frames per character
  deleteSpeed={1}
  pauseAfterType={60}  // Frames to wait before deleting
  delay={30}           // Start delay
  cursor=""
  style={{ fontSize: vmin * 2, fontFamily: 'monospace', whiteSpace: 'pre' }}
/>

// Multiple texts with cycling:
<TypeWriter
  text={["First line", "Second line", "Third line"]}
  deleteBeforeNext={true}
  loop={false}
  errorRate={0.05}     // 5% chance of typo
/>
tsx
<AnimatedCounter
  transition={{
    values: [0, 1000],            // 数值关键帧
    color: ['#ff0000', '#00ff00'], // 颜色过渡
    scale: [0.8, 1],
    duration: 30,
    delay: 10,
  }}
  prefix="$"
  postfix="+"
  toFixed={0}
  style={{ fontSize: vmin * 4, fontWeight: 'bold' }}
/>

CodeBlock

TypeWriter

tsx
<CodeBlock
  code={`const x = useViewportRect();\nconsole.log(x.vmin);`}
  language="tsx"
  theme="dark"
  showLineNumbers={false}
  fontSize={vmin * 1.2}
  padding={vmin * 1.5}
  transition={{
    duration: 20,
    delay: 10,
    lineStagger: 2,                        // Frames between lines
    lineStaggerDirection: "forward",       // "forward" | "reverse" | "center" | "random"
    opacity: [0, 1],
    y: [8, 0],
    blur: [10, 0],
  }}
  highlight={[{ lines: [2, 3], color: "rgba(59,130,246,0.15)", opacity: [0, 1] }]}
  focus={{ lines: [2, 3], dimOpacity: 0.3, dimBlur: 2 }}
/>
tsx
<TypeWriter
  text="import { TypeWriter } from 'remotion-bits';"
  typeSpeed={2}        // 每个字符的输入时长(帧)
  deleteSpeed={1}
  pauseAfterType={60}  // 输入完成后的暂停时长(帧)
  delay={30}           // 开始延迟(帧)
  cursor=""
  style={{ fontSize: vmin * 2, fontFamily: 'monospace', whiteSpace: 'pre' }}
/>

// 多文本循环:
<TypeWriter
  text={["First line", "Second line", "Third line"]}
  deleteBeforeNext={true}
  loop={false}
  errorRate={0.05}     // 5%的输入错误概率
/>

StaggeredMotion

CodeBlock

tsx
<StaggeredMotion
  transition={{
    opacity: [0, 1],
    scale: [0, 1],
    y: [20, 0],
    duration: 30,
    delay: 5,
    stagger: 3,                            // Frames between each child
    staggerDirection: "forward",           // "forward"|"reverse"|"center"|"random"
    easing: "easeOutCubic",
    borderRadius: 4,
  }}
>
  <div>Child 1</div>
  <div>Child 2</div>
  <div>Child 3</div>
</StaggeredMotion>
tsx
<CodeBlock
  code={`const x = useViewportRect();\nconsole.log(x.vmin);`}
  language="tsx"
  theme="dark"
  showLineNumbers={false}
  fontSize={vmin * 1.2}
  padding={vmin * 1.5}
  transition={{
    duration: 20,
    delay: 10,
    lineStagger: 2,                        // 每行的动画延迟(帧)
    lineStaggerDirection: "forward",       // "forward" | "reverse" | "center" | "random"
    opacity: [0, 1],
    y: [8, 0],
    blur: [10, 0],
  }}
  highlight={[{ lines: [2, 3], color: "rgba(59,130,246,0.15)", opacity: [0, 1] }]}
  focus={{ lines: [2, 3], dimOpacity: 0.3, dimBlur: 2 }}
/>

GradientTransition

StaggeredMotion

tsx
<GradientTransition
  gradient={[
    "linear-gradient(0deg, #051226, #1e0541)",
    "linear-gradient(180deg, #a5d4dd, #5674b1)",
  ]}
  duration={90}
  easing="easeInOut"
  shortestAngle={true}   // Interpolate angles via shortest path
/>
tsx
<StaggeredMotion
  transition={{
    opacity: [0, 1],
    scale: [0, 1],
    y: [20, 0],
    duration: 30,
    delay: 5,
    stagger: 3,                            // 每个子元素的动画延迟(帧)
    staggerDirection: "forward",           // "forward"|"reverse"|"center"|"random"
    easing: "easeOutCubic",
    borderRadius: 4,
  }}
>
  <div>Child 1</div>
  <div>Child 2</div>
  <div>Child 3</div>
</StaggeredMotion>

MatrixRain

GradientTransition

tsx
<MatrixRain fontSize={18} color="#00FF00" speed={1.2} density={0.8} streamLength={20} />
tsx
<GradientTransition
  gradient={[
    "linear-gradient(0deg, #051226, #1e0541)",
    "linear-gradient(180deg, #a5d4dd, #5674b1)",
  ]}
  duration={90}
  easing="easeInOut"
  shortestAngle={true}   // 通过最短路径插值角度
/>

ScrollingColumns

MatrixRain

tsx
<ScrollingColumns
  columns={[
    { images: ["/img1.jpg", "/img2.jpg"], speed: 100, direction: "up" },
    { images: ["/img3.jpg", "/img4.jpg"], speed: 150, direction: "down" },
  ]}
  height={rect.vmin * 40}
  gap={rect.vmin * 2}
/>
tsx
<MatrixRain fontSize={18} color="#00FF00" speed={1.2} density={0.8} streamLength={20} />

Particle System

ScrollingColumns

tsx
<Particles style={{ position: 'absolute', inset: 0 }}>
  <Spawner
    rate={2}           // Particles per frame
    max={200}          // Maximum alive particles
    lifespan={100}     // Frames each particle lives
    burst={50}         // Emit 50 on first frame (optional)
    position={resolvePoint(rect, "center")}
    area={{ width: rect.width, height: 0 }}  // Spawn area
    velocity={{ x: 0, y: -2, varianceX: 1, varianceY: 0.5 }}
  >
    {/* Multiple children = random selection per particle */}
    <div style={{ width: vmin * 2, height: vmin * 2, borderRadius: '50%', background: '#fff' }} />
    <div style={{ width: vmin * 1, height: vmin * 1, borderRadius: '50%', background: '#aaa' }} />
  </Spawner>

  <Behavior
    gravity={{ y: 0.1 }}                              // Constant force
    drag={0.96}                                        // Air resistance (1=none, 0=instant stop)
    wiggle={{ magnitude: 0.6, frequency: 0.25 }}       // Random oscillation
    opacity={[1, 0]}                                   // Fade out over lifetime
    scale={{ start: 1, end: 0.3, startVariance: 0.2 }} // Shrink over lifetime
  />
</Particles>

tsx
<ScrollingColumns
  columns={[
    { images: ["/img1.jpg", "/img2.jpg"], speed: 100, direction: "up" },
    { images: ["/img3.jpg", "/img4.jpg"], speed: 150, direction: "down" },
  ]}
  height={rect.vmin * 40}
  gap={rect.vmin * 2}
/>

Procedural Content Generation

粒子系统

For grids, backgrounds, and data-driven visuals, use deterministic
random()
:
tsx
import { random } from 'remotion-bits';

const items = useMemo(() => {
  const palette = ['#fb4934', '#b8bb26', '#fabd2f', '#83a598'];
  const result = [];

  for (let r = 0; r < 10; r++) {
    for (let c = 0; c < 20; c++) {
      const seed = `grid-${r}-${c}`;
      const colorIndex = Math.floor(random(seed + 'color') * palette.length);
      const dist = Math.sqrt(r * r + c * c);

      result.push(
        <StaggeredMotion key={`${r}-${c}`}
          transition={{
            opacity: [0, 1],
            scale: [0, 1],
            delay: 30 + dist * 2,  // Wave from top-left
            duration: 40,
          }}>
          <div style={{
            width: vmin * 5, height: vmin * 5,
            background: palette[colorIndex],
          }} />
        </StaggeredMotion>
      );
    }
  }
  return result;
}, [vmin]);
random(seed)
is deterministic — same seed always returns same value. Use unique string seeds per element.

tsx
<Particles style={{ position: 'absolute', inset: 0 }}>
  <Spawner
    rate={2}           // 每帧生成的粒子数
    max={200}          // 最大存活粒子数
    lifespan={100}     // 每个粒子的生命周期(帧)
    burst={50}         // 第一帧生成50个粒子(可选)
    position={resolvePoint(rect, "center")}
    area={{ width: rect.width, height: 0 }}  // 生成区域
    velocity={{ x: 0, y: -2, varianceX: 1, varianceY: 0.5 }}
  >
    {/* 多个子元素——每个粒子随机选择一个 */}
    <div style={{ width: vmin * 2, height: vmin * 2, borderRadius: '50%', background: '#fff' }} />
    <div style={{ width: vmin * 1, height: vmin * 1, borderRadius: '50%', background: '#aaa' }} />
  </Spawner>

  <Behavior
    gravity={{ y: 0.1 }}                              // 恒定重力
    drag={0.96}                                        // 空气阻力(1=无阻力,0=立即停止)
    wiggle={{ magnitude: 0.6, frequency: 0.25 }}       // 随机摆动
    opacity={[1, 0]}                                   // 生命周期内淡出
    scale={{ start: 1, end: 0.3, startVariance: 0.2 }} // 生命周期内缩小
  />
</Particles>

Utility Functions

程序化内容生成

interpolate (non-monotonic)

Unlike Remotion's built-in, supports non-monotonic input ranges:
tsx
import { interpolate, Easing } from "remotion-bits";

// Hold then animate
interpolate(frame, [0, 30, 30, 60], [0, 0, 100, 100]);

// With easing
interpolate(frame, [0, 60], [0, 100], { easing: "easeOutCubic" });

// Clamp extrapolation
interpolate(frame, [0, 60], [0, 100], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });
对于网格、背景和数据驱动的视觉效果,使用确定性的
random()
函数:
tsx
import { random } from 'remotion-bits';

const items = useMemo(() => {
  const palette = ['#fb4934', '#b8bb26', '#fabd2f', '#83a598'];
  const result = [];

  for (let r = 0; r < 10; r++) {
    for (let c = 0; c < 20; c++) {
      const seed = `grid-${r}-${c}`;
      const colorIndex = Math.floor(random(seed + 'color') * palette.length);
      const dist = Math.sqrt(r * r + c * c);

      result.push(
        <StaggeredMotion key={`${r}-${c}`}
          transition={{
            opacity: [0, 1],
            scale: [0, 1],
            delay: 30 + dist * 2,  // 从左上角开始的波浪式动画
            duration: 40,
          }}>
          <div style={{
            width: vmin * 5, height: vmin * 5,
            background: palette[colorIndex],
          }} />
        </StaggeredMotion>
      );
    }
  }
  return result;
}, [vmin]);
random(seed)
是确定性的——相同的种子始终返回相同的值。为每个元素使用唯一的字符串种子。

Color Interpolation

工具函数

interpolate(支持非单调)

tsx
import { interpolateColorKeyframes } from "remotion-bits";

// Perceptually uniform (Oklch) color transitions
const color = interpolateColorKeyframes(["#ff0000", "#00ff00", "#0000ff"], progress);
与Remotion内置的interpolate不同,该函数支持非单调输入范围:
tsx
import { interpolate, Easing } from "remotion-bits";

// 先保持,再动画
interpolate(frame, [0, 30, 30, 60], [0, 0, 100, 100]);

// 带缓动曲线
interpolate(frame, [0, 60], [0, 100], { easing: "easeOutCubic" });

// 限制插值范围
interpolate(frame, [0, 60], [0, 100], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });

Geometry

颜色插值

tsx
import { resolvePoint, createRect, Rect } from "remotion-bits";

const point = resolvePoint(rect, "center");       // { x: 960, y: 540 }
const point2 = resolvePoint(rect, { x: "50%", y: "25%" }); // { x: 960, y: 270 }

tsx
import { interpolateColorKeyframes } from "remotion-bits";

// 感知均匀的(Oklch)颜色过渡
const color = interpolateColorKeyframes(["#ff0000", "#00ff00", "#0000ff"], progress);

Bit Metadata Format

几何工具

Bits export a
metadata
object and a
Component
:
tsx
export const metadata = {
  name: "MyBit",
  description: "Description of the bit.",
  tags: ["tag1", "tag2"],
  duration: 300,          // Total frames
  width: 1920,
  height: 1080,
  registry: {
    name: "bit-my-bit",
    title: "My Bit",
    description: "Registry description.",
    type: "bit" as const,
    add: "when-needed" as const,
    registryDependencies: ["animated-text", "scene-3d", "use-viewport-rect"],
    dependencies: [],
    files: [{ path: "path/to/MyBit.tsx" }],
  },
};

export const Component: React.FC = () => { /* ... */ };

tsx
import { resolvePoint, createRect, Rect } from "remotion-bits";

const point = resolvePoint(rect, "center");       // { x: 960, y: 540 }
const point2 = resolvePoint(rect, { x: "50%", y: "25%" }); // { x: 960, y: 270 }

Common Patterns

Bit元数据格式

Floating Card Container

tsx
const FloatingCard = ({ children }) => (
  <div style={{
    position: 'relative',
    width: vmin * 60, height: vmin * 30,
    background: 'rgba(20, 20, 30, 0.6)',
    backdropFilter: 'blur(10px)',
    border: '1px solid rgba(255,255,255,0.2)',
    borderRadius: vmin * 1,
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    overflow: 'hidden',
    boxShadow: '0 0 20px rgba(0,0,0,0.2)',
  }}>
    {children}
  </div>
);
Bits会导出
metadata
对象和
Component
组件:
tsx
export const metadata = {
  name: "MyBit",
  description: "Bit的描述。",
  tags: ["tag1", "tag2"],
  duration: 300,          // 总帧数
  width: 1920,
  height: 1080,
  registry: {
    name: "bit-my-bit",
    title: "My Bit",
    description: "注册表描述。",
    type: "bit" as const,
    add: "when-needed" as const,
    registryDependencies: ["animated-text", "scene-3d", "use-viewport-rect"],
    dependencies: [],
    files: [{ path: "path/to/MyBit.tsx" }],
  },
};

export const Component: React.FC = () => { /* ... */ };

Scene with Entry Animations per Step

常见模式

悬浮卡片容器

tsx
<Step id="particles-demo" {...cardPosition.toProps()}
  transition={{ opacity: [0, 1], blur: [10, 0] }}>
  <FloatingCard>
    {/* Content appears when camera arrives */}
  </FloatingCard>
</Step>
tsx
const FloatingCard = ({ children }) => (
  <div style={{
    position: 'relative',
    width: vmin * 60, height: vmin * 30,
    background: 'rgba(20, 20, 30, 0.6)',
    backdropFilter: 'blur(10px)',
    border: '1px solid rgba(255,255,255,0.2)',
    borderRadius: vmin * 1,
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    overflow: 'hidden',
    boxShadow: '0 0 20px rgba(0,0,0,0.2)',
  }}>
    {children}
  </div>
);

Step-Responsive Title Tracking Camera

带步骤入场动画的场景

tsx
<StepResponsive
  centered
  style={{ fontSize: vmin * 10, position: 'absolute', width: 'max-content' }}
  steps={{
    'intro': { transform: [startPos] },
    'main': { transform: [mainPos] },
    'outro': { transform: [mainPos, endPos], duration: "step", easing: "easeInOutCubic" },
  }}
>
  <h1>Section Title</h1>
</StepResponsive>
tsx
<Step id="particles-demo" {...cardPosition.toProps()}
  transition={{ opacity: [0, 1], blur: [10, 0] }}>
  <FloatingCard>
    {/* 相机到达时内容显示 */}
  </FloatingCard>
</Step>

Animated Grid with Wave Timing

跟随相机的Step-Responsive标题

tsx
const dist = Math.sqrt(Math.pow(r - centerRow, 2) + Math.pow(c - centerCol, 2));
const delay = 30 + dist * 2; // Ripple outward from center

<StaggeredMotion transition={{ opacity: [0, 1], scale: [0, 1], delay, duration: 40 }}>
  <div style={{ width: size, height: size, background: color }} />
</StaggeredMotion>
tsx
<StepResponsive
  centered
  style={{ fontSize: vmin * 10, position: 'absolute', width: 'max-content' }}
  steps={{
    'intro': { transform: [startPos] },
    'main': { transform: [mainPos] },
    'outro': { transform: [mainPos, endPos], duration: "step", easing: "easeInOutCubic" },
  }}
>
  <h1>章节标题</h1>
</StepResponsive>

Exit Transitions on Steps

波浪式动画的网格

tsx
<Step id="transitions" duration={120} {...pos.toProps()}
  exitTransition={{ blur: [0, 10], opacity: [1, 0] }}>
  {/* Content blurs out when camera leaves */}
</Step>
tsx
const dist = Math.sqrt(Math.pow(r - centerRow, 2) + Math.pow(c - centerCol, 2));
const delay = 30 + dist * 2; // 从中心向外的波浪式延迟

<StaggeredMotion transition={{ opacity: [0, 1], scale: [0, 1], delay, duration: 40 }}>
  <div style={{ width: size, height: size, background: color }} />
</StaggeredMotion>

Element3D with Transform Keyframes

带退场动画的Step

tsx
<Element3D centered
  style={{ width: vmin * 32, height: vmin * 24 }}
  transition={{
    delay: 10,
    opacity: [0, 1],
    duration: 35,
    transform: [
      base.translate(0, vmin * 20, 0),  // Start below
      base.translate(0, 0, 0),           // End at position
    ],
    easing: 'easeInOutCubic',
  }}>
  <div>slides up into place</div>
</Element3D>

tsx
<Step id="transitions" duration={120} {...pos.toProps()}
  exitTransition={{ blur: [0, 10], opacity: [1, 0] }}>
  {/* 相机离开时内容模糊淡出 */}
</Step>

Reference Documentation

带变换关键帧的Element3D

For exhaustive API details:
  • references/components.md - Complete component props tables
  • references/utilities.md - All utility functions
  • references/patterns.md - More animation recipes and examples
tsx
<Element3D centered
  style={{ width: vmin * 32, height: vmin * 24 }}
  transition={{
    delay: 10,
    opacity: [0, 1],
    duration: 35,
    transform: [
      base.translate(0, vmin * 20, 0),  // 起始位置在下方
      base.translate(0, 0, 0),           // 结束位置
    ],
    easing: 'easeInOutCubic',
  }}>
  <div>向上滑动到位</div>
</Element3D>

参考文档

如需完整的API细节:
  • references/components.md - 完整的组件属性表
  • references/utilities.md - 所有工具函数
  • references/patterns.md - 更多动画示例与技巧