Skip to content

Animation

Keyframe any of x, y, scale, rotation, opacity, volume per clip. Times are clip-relative microseconds. Plays in preview and bakes into every export automatically.

Fade a clip in

project.dispatch({ type: "keyframe/set", payload: { clipId: "clip-1", property: "opacity", timeUs: 0, value: 0, easing: "easeOut" } });
project.dispatch({ type: "keyframe/set", payload: { clipId: "clip-1", property: "opacity", timeUs: 1_000_000, value: 1 } });

Slow push-in

project.dispatch({ type: "keyframe/set", payload: { clipId: "clip-1", property: "scale", timeUs: 0, value: 1 } });
project.dispatch({ type: "keyframe/set", payload: { clipId: "clip-1", property: "scale", timeUs: 5_000_000, value: 1.15 } });

Duck the audio under a voiceover

project.dispatch({ type: "keyframe/set", payload: { clipId: "music", property: "volume", timeUs: 2_000_000, value: 1 } });
project.dispatch({ type: "keyframe/set", payload: { clipId: "music", property: "volume", timeUs: 2_400_000, value: 0.2 } });

Volume animates as smooth gain ramps (no stepping/zipper noise), identically in preview and export.

Easings

The easing on a keyframe shapes the segment to the next keyframe. Default: linear.

EasingEffect
linearConstant rate
easeIn / easeOut / easeInOutAccelerate / decelerate / both
holdKeep the value, jump at the next keyframe
{ kind: "bezier", x1, y1, x2, y2 }Custom cubic bézier (presets are sugar for these)

Every preset is stored as its bézier control points, so a custom curve is a first-class citizen — no registration needed:

project.dispatch({
  type: "keyframe/set",
  payload: { clipId: "clip-1", property: "y", timeUs: 0, value: 0.6,
             easing: { kind: "bezier", x1: 0.34, y1: 1.56, x2: 0.64, y2: 1 } }, // overshoot/bounce
});

Edit and remove

// Setting a keyframe at an existing time updates it in place:
project.dispatch({ type: "keyframe/set", payload: { clipId: "clip-1", property: "opacity", timeUs: 0, value: 0.5 } });

// Remove one (timeUs must match exactly), or clear a property — or everything:
project.dispatch({ type: "keyframe/remove", payload: { clipId: "clip-1", property: "opacity", timeUs: 0 } });
project.dispatch({ type: "keyframe/clear", payload: { clipId: "clip-1", property: "opacity" } });
project.dispatch({ type: "keyframe/clear", payload: { clipId: "clip-1" } });

Good to know

  • Keyframes anchor to the clip’s visible start; trimming the clip keeps them.
  • Full payload schemas: command catalog.