framefields-compose
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseframefields-compose
framefields-compose
Setup
安装设置
Import everything from the npm package. If the project doesn't have framefields set up yet, follow the skill first (install, project scaffold, first render).
framefieldsframefields从 npm 包中导入所有内容。如果项目尚未配置 framefields,请先按照 技能操作(安装、项目脚手架搭建、首次渲染)。
framefieldsframefieldsOverview
概述
framefieldsEvery composition is defined as a tree of layout nodes compiled into a deterministic execution program: .
(composition, frame) → pixelsframefields每个合成作品都被定义为一棵布局节点树,会被编译为确定性执行程序:。
(composition, frame) → pixelsArchitecture & Core Concepts
架构与核心概念
1. Composition Lifecycle
1. 合成生命周期
A instance defines canvas dimensions, frame rate, background, registered fonts, global audio tracks, and the layer hierarchy:
Compositiontypescript
import { Composition, Layer, FontManager } from "framefields";
// Always register fonts before assembling layers
await FontManager.register({
family: "Inter",
source: "assets/fonts/Inter.ttf"
});
const comp = new Composition({
width: 1920,
height: 1080,
fps: 30,
durationMs: 10000, // or computeDuration()
backgroundColor: "#0E0D0C",
fonts: ["assets/fonts/Inter.ttf"],
});
comp.add(Layer.box({ /* ... */ }));
comp.add(Layer.audio("assets/score.mp3", { volume: 1 }));一个 实例定义了画布尺寸、帧率、背景、已注册字体、全局音轨和图层层级:
Compositiontypescript
import { Composition, Layer, FontManager } from "framefields";
// Always register fonts before assembling layers
await FontManager.register({
family: "Inter",
source: "assets/fonts/Inter.ttf"
});
const comp = new Composition({
width: 1920,
height: 1080,
fps: 30,
durationMs: 10000, // or computeDuration()
backgroundColor: "#0E0D0C",
fonts: ["assets/fonts/Inter.ttf"],
});
comp.add(Layer.box({ /* ... */ }));
comp.add(Layer.audio("assets/score.mp3", { volume: 1 }));2. Scene Architecture (Production Pattern)
2. 场景架构(生产模式)
For commercials, films, and multi-shot videos, break the project into discrete, modular scene functions using scene-local frames:
- Tokens first (): Centralize color palette, typography, easing curves, and a tempo-locked beat grid (
theme.ts). Never use arbitrary margins or ad-hoc hex codes.BEAT = Math.round(fps * 60 / BPM) - Scene modules (): Each scene returns a full-frame root box with
scenes/hero.tsandstartFrame. Children position themselves relative to scene start (durationFrames= scene onset).frame: 0 - Master assembly (): Imports scenes, mounts them to
film.ts, attaches the master soundtrack and composition-level grading/grain.comp.add(scene())
typescript
// theme.ts
export const W = 1920;
export const H = 1080;
export const FPS = 30;
export const BEAT = 18; // 100 BPM at 30 fps
export const beats = (n: number) => Math.round(n * BEAT);
export const INK = "#0E0D0C";
export const PAPER = "#EEE9E0";
export const EMBER = "#FF5A1F";
export function scene(options: { id: string; from: number; to: number; children: unknown[] }) {
return Layer.box({
id: options.id,
position: "absolute",
x: 0,
y: 0,
width: W,
height: H,
startFrame: options.from,
durationFrames: options.to - options.from,
children: options.children as never,
});
}对于商业广告、电影和多镜头视频,可使用场景局部帧将项目拆分为离散的模块化场景函数:
- 令牌优先(): 集中管理调色板、排版、缓动曲线和与节奏锁定的节拍网格(
theme.ts)。切勿使用随意的边距或临时的十六进制颜色代码。BEAT = Math.round(fps * 60 / BPM) - 场景模块(): 每个场景返回一个带
scenes/hero.ts和startFrame的全帧根容器。子元素相对于场景起始位置定位(durationFrames= 场景开始)。frame: 0 - 主装配(): 导入场景,将其挂载到
film.ts,附加主音轨和合成级别的调色/颗粒效果。comp.add(scene())
typescript
// theme.ts
export const W = 1920;
export const H = 1080;
export const FPS = 30;
export const BEAT = 18; // 100 BPM at 30 fps
export const beats = (n: number) => Math.round(n * BEAT);
export const INK = "#0E0D0C";
export const PAPER = "#EEE9E0";
export const EMBER = "#FF5A1F";
export function scene(options: { id: string; from: number; to: number; children: unknown[] }) {
return Layer.box({
id: options.id,
position: "absolute",
x: 0,
y: 0,
width: W,
height: H,
startFrame: options.from,
durationFrames: options.to - options.from,
children: options.children as never,
});
}Layer Primitives (Layer.*
)
Layer.*图层基元(Layer.*
)
Layer.*All builders return instances with chainable , , and effect methods.
Layer.*AnimatableNode<T>.animate().apply()| Builder | Node | Description & Key Options |
|---|---|---|
| | Slug GPU vector text. Options: |
| | Rectangular container / card. Options: |
| | Auto-layout container (Yoga). Options: |
| | Video layer decoded via mediabunny. Options: |
| | Static raster image ( |
| | Vector SVG asset. Options: |
| | Lottie vector animation file. |
| | Audio track layer. Options: |
| | Parametric vector graphics ( |
| | SRT-driven synchronized subtitle engine. Bottom-anchored auto-wrapping cues. |
| | Charts built from native nodes (d3 scales and geometry). |
| | 3D scene camera with FOV, lens rack focus, and orbital coordinates. |
| | 3D lighting primitive ( |
| | 3D mesh loader ( |
所有 构建器都返回 实例,支持链式调用 、 和效果方法。
Layer.*AnimatableNode<T>.animate().apply()| 构建器 | 节点 | 说明与关键选项 |
|---|---|---|
| | Slug GPU 矢量文本。选项: |
| | 矩形容器/卡片。选项: |
| | 自动布局容器(Yoga)。选项: |
| | 通过 mediabunny 解码的视频图层。选项: |
| | 静态光栅图像( |
| | 矢量 SVG 资源。选项: |
| | Lottie 矢量动画文件。 |
| | 音轨图层。选项: |
| | 参数化矢量图形( |
| | 基于 SRT 的同步字幕引擎。底部锚定的自动换行提示。 |
| | 基于原生节点构建的图表(d3 比例尺和几何)。 |
| | 3D 场景相机,支持视场角、镜头跟焦和轨道坐标。 |
| | 3D 光照基元( |
| | 3D 网格加载器( |
Animation System (LayerAnimation
)
LayerAnimation动画系统(LayerAnimation
)
LayerAnimationCreate animations using or fluent factory methods. Animations are composed of keyframed tracks or procedural drivers.
LayerAnimation.create()使用 或流式工厂方法创建动画。动画由关键帧轨道或过程驱动组成。
LayerAnimation.create()1. Keyframe Transitions (fromTo
)
fromTo1. 关键帧过渡(fromTo
)
fromTotypescript
import { LayerAnimation } from "framefields";
// Fluent chain
const anim = LayerAnimation.create()
.fadeIn(0, 20, "power2.out")
.fromTo("y", 60, 0, { start: 0, end: 24, ease: "expo.out" })
.fromTo("scale", 0.95, 1.0, { start: 0, end: 30, ease: "power3.out" });
// Easing bases: none, power1, power2, power3, sine, circ, expo, back, elastic, bounce, spring, cubic, hold
// Directions: in, out, inOut (e.g. "expo.out", "back.out(1.7)", "power2.inOut")For more than two keyframes on one prop, list them with : each tuple is , where the ease shapes the motion into that keyframe. Frames are rounded, so beat math () can go straight in:
keys[frame, value, ease?]at + BEAT / 2typescript
const pop = LayerAnimation.create()
.keys("scale", [[0, 0], [10, 1.12, "back.out(1.6)"], [16, 1, "power2.out"]])
.keys("opacity", [[0, 0], [6, 1]]);
// Static form: LayerAnimation.keys("rotation", [[0, -8], [20, 0, "expo.out"]])Keyframes past the layer's never play; the render warns () and reports it.
durationFramesanimation_truncatedrenderVideo({ qa: true })typescript
import { LayerAnimation } from "framefields";
// Fluent chain
const anim = LayerAnimation.create()
.fadeIn(0, 20, "power2.out")
.fromTo("y", 60, 0, { start: 0, end: 24, ease: "expo.out" })
.fromTo("scale", 0.95, 1.0, { start: 0, end: 30, ease: "power3.out" });
// Easing bases: none, power1, power2, power3, sine, circ, expo, back, elastic, bounce, spring, cubic, hold
// Directions: in, out, inOut (e.g. "expo.out", "back.out(1.7)", "power2.inOut")如果一个属性有两个以上的关键帧,请使用 列出:每个元组为 ,其中缓动定义了进入该关键帧的运动曲线。帧号会被取整,因此节拍计算()可以直接传入:
keys[frame, value, ease?]at + BEAT / 2typescript
const pop = LayerAnimation.create()
.keys("scale", [[0, 0], [10, 1.12, "back.out(1.6)"], [16, 1, "power2.out"]])
.keys("opacity", [[0, 0], [6, 1]]);
// Static form: LayerAnimation.keys("rotation", [[0, -8], [20, 0, "expo.out"]])超过图层 的关键帧永远不会播放;渲染时会发出警告(), 会报告此问题。
durationFramesanimation_truncatedrenderVideo({ qa: true })2. Kinetic Typography (TextAnimator
)
TextAnimator2. 动态排版(TextAnimator
)
TextAnimatorText animators operate at the GPU glyph instancing level, cascading offsets across characters, words, or lines:
typescript
import { Layer, LayerAnimation, TextAnimator } from "framefields";
// Letter-by-letter wave rise with perspective tilt and blur
const headlineNode = Layer.text("PRECISION MOTION", {
fontSize: 96,
fontFamily: "Inter",
fill: "#FFFFFF",
animators: [
TextAnimator.waveRise({
y: 40,
rotationX: 45,
opacity: 0,
blur: 10,
easing: "expo.in"
})
]
}).animate(
// Sweep the selection offset across the text string
LayerAnimation.create().kineticSweep(-1, 1, 0, 24, "power2.inOut")
);Presets available: , , , , .
TextAnimator.waveRise()TextAnimator.wordPop()TextAnimator.blurIn()TextAnimator.flip3D()TextAnimator.colorSweep()文本动画器在 GPU 字形实例化层面运行,在字符、单词或行之间级联偏移:
typescript
import { Layer, LayerAnimation, TextAnimator } from "framefields";
// Letter-by-letter wave rise with perspective tilt and blur
const headlineNode = Layer.text("PRECISION MOTION", {
fontSize: 96,
fontFamily: "Inter",
fill: "#FFFFFF",
animators: [
TextAnimator.waveRise({
y: 40,
rotationX: 45,
opacity: 0,
blur: 10,
easing: "expo.in"
})
]
}).animate(
// Sweep the selection offset across the text string
LayerAnimation.create().kineticSweep(-1, 1, 0, 24, "power2.inOut")
);可用预设:、、、、。
TextAnimator.waveRise()TextAnimator.wordPop()TextAnimator.blurIn()TextAnimator.flip3D()TextAnimator.colorSweep()3. 3D Spatial Transforms & Camera Motion
3. 3D 空间变换与相机运动
Layers can be transformed in 3D perspective space:
typescript
const card = Layer.box({
width: 600,
height: 400,
background: "#181715",
borderRadius: 24,
transformStyle: "preserve-3d",
perspective: 1200,
}).animate(
LayerAnimation.create()
.fromTo("rotateX", 25, 0, { start: 0, end: 30, ease: "expo.out" })
.fromTo("rotateY", -35, 0, { start: 0, end: 30, ease: "expo.out" })
.fromTo("translateZ", -200, 0, { start: 0, end: 30, ease: "expo.out" })
);For full 3D camera staging, use :
CameraAnimationtypescript
import { CameraAnimation } from "framefields";
const camAnim = CameraAnimation.camera()
.orbit({
azimuth: { from: -45, to: 15 },
elevation: { from: 10, to: 25 },
radius: { from: 2000, to: 1400 },
start: 0,
end: 90,
ease: "sine.inOut"
})
.rackFocus({
fromDistance: 2000,
toDistance: 1400,
fromFStop: 1.8,
toFStop: 4.0,
start: 20,
end: 60
});图层可在 3D 透视空间中变换:
typescript
const card = Layer.box({
width: 600,
height: 400,
background: "#181715",
borderRadius: 24,
transformStyle: "preserve-3d",
perspective: 1200,
}).animate(
LayerAnimation.create()
.fromTo("rotateX", 25, 0, { start: 0, end: 30, ease: "expo.out" })
.fromTo("rotateY", -35, 0, { start: 0, end: 30, ease: "expo.out" })
.fromTo("translateZ", -200, 0, { start: 0, end: 30, ease: "expo.out" })
);对于完整的 3D 相机调度,请使用 :
CameraAnimationtypescript
import { CameraAnimation } from "framefields";
const camAnim = CameraAnimation.camera()
.orbit({
azimuth: { from: -45, to: 15 },
elevation: { from: 10, to: 25 },
radius: { from: 2000, to: 1400 },
start: 0,
end: 90,
ease: "sine.inOut"
})
.rackFocus({
fromDistance: 2000,
toDistance: 1400,
fromFStop: 1.8,
toFStop: 4.0,
start: 20,
end: 60
});4. Procedural & Audio-Reactive Drivers
4. 过程式与音频响应驱动
Properties can be bound to Perlin wiggles, harmonic springs, or audio-extracted signals:
typescript
// Organic floating drift
const floatAnim = LayerAnimation.create()
.wiggle("x", { frequency: 1.5, amplitude: 25 })
.wiggle("y", { frequency: 1.2, amplitude: 20 });
// Physics spring overshoot
const springAnim = LayerAnimation.create()
.spring("scale", { damping: 14, stiffness: 190, mass: 1 });
// Audio reactivity: pass the signal object straight in…
const kick = Signal.fromArray(kickEnvelope, fps); // or Signal.builder({ type: "custom", fn })
const beatAnim = LayerAnimation.create()
.signal("scale", kick, { multiplier: 0.12, offset: 1.0, smoothing: 2 });
// …or register it once by name and bind the name (handy when many layers share it)
comp.addSignal("drum_stem_signal", kick);
const beatAnim2 = LayerAnimation.create()
.signal("opacity", "drum_stem_signal", { multiplier: 0.5, offset: 0.5 });.signal().colorSignal()value × multiplier + offsetinline_signal_Ncomp.addSignalundefinedctx.frame属性可绑定到 Perlin 抖动、谐波弹簧或音频提取的信号:
typescript
// Organic floating drift
const floatAnim = LayerAnimation.create()
.wiggle("x", { frequency: 1.5, amplitude: 25 })
.wiggle("y", { frequency: 1.2, amplitude: 20 });
// Physics spring overshoot
const springAnim = LayerAnimation.create()
.spring("scale", { damping: 14, stiffness: 190, mass: 1 });
// Audio reactivity: pass the signal object straight in…
const kick = Signal.fromArray(kickEnvelope, fps); // or Signal.builder({ type: "custom", fn })
const beatAnim = LayerAnimation.create()
.signal("scale", kick, { multiplier: 0.12, offset: 1.0, smoothing: 2 });
// …or register it once by name and bind the name (handy when many layers share it)
comp.addSignal("drum_stem_signal", kick);
const beatAnim2 = LayerAnimation.create()
.signal("opacity", "drum_stem_signal", { multiplier: 0.5, offset: 0.5 });.signal().colorSignal()值 × 乘数 + 偏移量inline_signal_Ncomp.addSignalundefinedctx.frameCritical Authoring Rules & Gotchas
关键创作规则与常见坑点
- Local Keyframe Coordinates: Keyframes are strictly relative to the node's own timeline window (= node appearance). Never offset keyframe frames by the parent's
0.startFrame - Coordinate Overrides: Animating overrides the node's layout
y. If a node is placed aty, animatey: 400, NOTfromTo("y", 440, 400).fromTo("y", 40, 0) - No String Styles for Borders: Use and
borderColor. CSSborderWidthis silently ignored.border: "1px solid red" - Font Registration is Mandatory: In headless WebGPU, system fonts do not exist. Every font must be registered via and listed in
FontManager.register(). Emoji need a color emoji font too: framefields ships none, so register one withcomp.fonts(a CBDT/CBLC bitmap font such as Noto Color Emoji). Without it exports draw nothing for emoji.FontManager.registerEmojiFont("assets/fonts/NotoColorEmoji.ttf") - Integer Frames Only: Keyframe frames, start frames, and durations must be integers. Decimal frames cause schema validation errors.
- Relative vs. Absolute: Providing or
xwithoutyturns the node into an absolute-positioned layer removed from flex flow.position: "relative" - Audio Layer Duration: Every must be assigned
Layer.audio()to prevent audio truncation in headless rendering.durationFrames - Premultiplied Alpha: Alpha shaders operate with premultiplied alpha. Never use factor in custom blends.
"src-alpha"
- 局部关键帧坐标: 关键帧严格相对于节点自身的时间线窗口(= 节点出现)。切勿用父级的
0偏移关键帧的帧号。startFrame - 坐标覆盖: 对 做动画会覆盖节点的布局
y。如果节点的位置是y,动画应写y: 400,而不是fromTo("y", 440, 400)。fromTo("y", 40, 0) - 边框不支持字符串样式: 请使用 和
borderColor。CSS 的borderWidth会被静默忽略。border: "1px solid red" - 字体注册是强制性的: 在无头 WebGPU 中,不存在系统字体。每个字体都必须通过 注册,并列入
FontManager.register()。表情符号也需要彩色表情字体:framefields 不附带任何表情字体,因此请使用comp.fonts注册一个(CBDT/CBLC 位图字体,如 Noto Color Emoji)。没有它的话,导出时表情符号不会显示任何内容。FontManager.registerEmojiFont("assets/fonts/NotoColorEmoji.ttf") - 仅支持整数帧: 关键帧帧号、起始帧和持续时间必须是整数。小数帧会导致模式验证错误。
- 相对与绝对: 如果提供了 或
x但没有设置y,节点会变成绝对定位图层,从弹性流中移除。position: "relative" - 音频图层持续时间: 每个 都必须指定
Layer.audio(),以防止无头渲染中的音频被截断。durationFrames - 预乘 Alpha: Alpha 着色器使用预乘 Alpha 运行。切勿在自定义混合中使用 因子。
"src-alpha"