Every frame is a function of its number
Section titled “Every frame is a function of its number”A composition compiles to a deterministic program: (composition, frame) → pixels. There are no timers and no wall clock. Rendering frame 45 always produces the same image, in any order, on any run. This is what makes frames testable and renders parallel.
Composition
Section titled “Composition”import { Composition } from "framefields";
const comp = new Composition({ width: 1920, height: 1080, fps: 30, // default 60 durationMs: 10_000, // or durationFrames; omitted → computed from media backgroundColor: "#0e0d0c", // default "#000000" antialias3d: true, // 4x MSAA for 3D meshes; false saves GPU memory});| Method | Does |
|---|---|
add(layer | layer[]) |
adds layers (or arrays, e.g. from Lighting.studio()) to the root |
addAudio(Layer.audio(...)) |
adds a soundtrack |
apply(effect) |
applies an effect to the whole frame |
addSignal(name, signal) |
registers a named signal layers can bind by name |
withVision(options) |
runs vision on the composition, returns reactive signals |
lint() |
document checks, no GPU |
toSpec() |
the plain-JSON program (validate with CompositorProgramSchema) |
renderFrame / renderFrameGrid / renderVideo |
output, see Rendering |
computeDuration() |
duration in ms, from options or media |
Layers
Section titled “Layers”Every Layer.* builder returns a node you can chain: .animate(), .withEffect(), .withMatte(), .withVision(), .pinToObject(). Layers nest through children.
Layer.box({ width: 720, height: 380, padding: 32, background: "#141721", borderRadius: 24, borderColor: "#262b3d", borderWidth: 1.5, children: [ Layer.flex({ dir: "column", gap: 16, children: [ Layer.text("ENGINE", { fontSize: 16, fontWeight: 700, fill: "#6366f1", letterSpacing: 2 }), Layer.text("WebGPU motion", { fontSize: 48, fontWeight: 700, fill: "#f8fafc" }), ], }), ],});startFrameanddurationFramesplace a layer on its parent’s timeline.- Keyframes are local: frame 0 is the layer’s first visible frame. Never add the parent’s
startFrameto keyframe times. - Frames are integers. Keyframe frames, start frames and durations must be whole numbers;
.keys()rounds for you, other APIs reject decimals. - Keyframes past a layer’s
durationFramesnever play (lint/QA:animation_truncated). - Signals are sampled at the composition frame, not the layer’s local frame.
A beat grid keeps timing musical and readable:
export const FPS = 30;export const BPM = 100;export const BEAT = Math.round((FPS * 60) / BPM); // 18 framesexport const beats = (n: number) => Math.round(n * BEAT);Layout and position
Section titled “Layout and position”- Containers lay children out with flexbox (Yoga):
Layer.flex({ dir, gap, justify, align, wrap, padding }). - Setting
xorywithoutposition: "relative"makes a layer absolute — removed from flex flow, positioned in its parent. - Animating
x/yoverrides layout. A layer placed aty: 400animates withfromTo("y", 440, 400), notfromTo("y", 40, 0). - Borders are
borderColor+borderWidth. A CSS shorthand likeborder: "1px solid red"is ignored.
Coordinates
Section titled “Coordinates”- 2D: pixels, origin top-left, y down.
- 3D: the camera is calibrated so
z = 0maps 1:1 onto canvas pixels. y is still down, so “up” is −y. See 3D.
Colors
Section titled “Colors”Every color prop takes hex, rgb(), hsl(), oklch(), oklab() or a CSS name. Color keyframes blend in OKLab, so red → green passes through yellow. Gradients are CSS strings (linear-, radial-, conic-gradient()) usable as any background or shape fill.
Project shape
Section titled “Project shape”src/theme.ts tokens: size, fps, colors, fonts, beat gridsrc/scenes/*.ts one function per scene, each returns a full-frame boxsrc/film.ts buildFilm(): fonts, Composition, Layer.sequence(scenes), gradesrc/render.ts frames | grid | preview | videoassets/ fonts, footage, audio, images, LUTs, models — in Git LFSoutput/ renders, gitignoredMedia in assets/ is stored with Git LFS: push it with a normal git push, and run git lfs pull in any fresh checkout before rendering. See Assets and Git LFS.