Skip to content

Animation

This content is for v0.4. Switch to the latest version for up-to-date documentation.

Almost every value you can change over time is written the same way, as constant or keyframed. This page covers that form, the interpolation curves, and the one thing that trips people up: which clock a keyframe’s time is measured on.

An animatable value is an object with a mode:

{ "mode": "constant", "value": 0.5 }
{
"mode": "keyframes",
"keyframes": [
{ "time": 0, "value": 0, "interpolation": { "type": "linear" } },
{ "time": 60000, "value": 1, "interpolation": { "type": "hold" } }
]
}
  • Keyframes must be in strictly ascending time order. The engine refuses a sequence out of order.
  • Before the first keyframe the value is the first keyframe’s; after the last, the last one’s.
  • A keyframe’s interpolation says how the value moves from it to the next keyframe. The last keyframe’s interpolation is never used, so hold is a natural choice there.

The value type depends on the field: a number for opacity, { "x", "y" } for a position or scale, a paint, an effect value ({ "type": "float", "value": 0.4 }) for an effect parameter, a string for a layout text. The schema lists them as Animated_float, Animated_Vec2, Animated_EffectValue and so on (reference).

Interpolation has three types:

Type Moves to the next value
hold Not at all: the value jumps when the next keyframe arrives.
linear At a constant rate.
cubic_bezier Along a CSS cubic-bezier(x1, y1, x2, y2) timing curve. x1 and x2 are in 0..1; y1 and y2 may overshoot.

alpha2/project has the CSS named curves as values: EASING_PRESETS.ease is cubic_bezier(0.25, 0.1, 0.25, 1), and there are easeIn, easeOut, easeInOut and linear, plus cubicBezier(x1, y1, x2, y2) and HOLD.

Values interpolate numerically: a rotation from 0 to 720 degrees turns twice. Normalize your keyframes if you want the shortest path.

Easing, used by transitions, is the same without hold.

Some values never interpolate:

  • enum and bool effect parameters only hold.
  • Strings (layout text) only hold.
  • An interpolated effect parameter that overshoots its range is clamped to it. A keyframe value you author outside the range is an error.

A position (AnimatedPoint) can also travel along a curve: give its keyframes an out_tangent and the next one an in_tangent, and the point follows the cubic Bezier through them. The interpolation then sets how fast it travels along that path.

A keyframe’s time is in ticks, but measured from different starting points depending on who owns the value:

Owner Keyed in Starts at zero when
A clip’s visual, its masks, a timeline transition’s parameters Timeline time The project starts.
A composition node’s settings (effect parameters, transforms) Composition time The clip starts (by default; see ClipComposition).
Shapes and layout generators Picture time The source time the clip selects is zero.

The reasoning: a clip’s visual places the clip on the timeline, so it is keyed in timeline time. A composition moves with the clip that shows it. A generated picture is a source, trimmed and placed by its clips just as a video file is.

This project uses all three:

{
"schema_version": 1,
"id": "animation",
"name": "Animation time bases",
"settings": { "width": 1280, "height": 720, "frame_rate": { "num": 30, "den": 1 }, "sample_rate": 48000 },
"sources": [
{ "id": "boathouse", "kind": "image", "locator": "asset://boathouse" },
{
"id": "dot",
"kind": "generated",
"generator": {
"type": "shapes",
"items": [
{
"type": "ellipse",
"center": {
"mode": "keyframes",
"keyframes": [
{
"time": 0,
"value": { "x": 0.15, "y": 0.8 },
"interpolation": { "type": "cubic_bezier", "x1": 0.25, "y1": 0.1, "x2": 0.25, "y2": 1 }
},
{
"time": 240000,
"value": { "x": 0.85, "y": 0.8 },
"interpolation": { "type": "hold" }
}
]
},
"size": { "mode": "constant", "value": { "x": 0.06, "y": 0.1067 } }
},
{
"type": "fill",
"paint": {
"mode": "constant",
"value": { "type": "solid", "color": { "red": 1, "green": 0.7, "blue": 0.1, "alpha": 1 } }
}
}
]
}
}
],
"compositions": [
{
"id": "focus",
"nodes": [
{ "id": "picture", "op": { "type": "clip" } },
{
"id": "blur",
"op": {
"type": "effect",
"input": "picture",
"effect": {
"id": "alpha2.effect.gaussian-blur",
"version": 1,
"params": {
"radius": {
"mode": "keyframes",
"keyframes": [
{
"time": 0,
"value": { "type": "float", "value": 0.03 },
"interpolation": { "type": "linear" }
},
{
"time": 120000,
"value": { "type": "float", "value": 0 },
"interpolation": { "type": "hold" }
}
]
}
}
}
}
}
],
"output": "blur"
}
],
"timeline": {
"tracks": [
{
"id": "picture",
"name": "Picture",
"kind": "video",
"clips": [
{
"id": "photo",
"source_id": "boathouse",
"timeline_range": { "start": 0, "end": 480000 },
"source_range": { "start": 0, "end": 480000 },
"composition": { "id": "focus" },
"visual": {
"scale": {
"mode": "keyframes",
"keyframes": [
{
"time": 0,
"value": { "x": 1, "y": 1 },
"interpolation": { "type": "linear" }
},
{
"time": 480000,
"value": { "x": 1.15, "y": 1.15 },
"interpolation": { "type": "hold" }
}
]
},
"opacity": {
"mode": "keyframes",
"keyframes": [
{
"time": 0,
"value": 0,
"interpolation": { "type": "cubic_bezier", "x1": 0.25, "y1": 0.1, "x2": 0.25, "y2": 1 }
},
{ "time": 60000, "value": 1, "interpolation": { "type": "hold" } }
]
}
}
}
]
},
{
"id": "overlay",
"name": "Overlay",
"kind": "video",
"clips": [
{
"id": "dot-clip",
"source_id": "dot",
"timeline_range": { "start": 120000, "end": 420000 },
"source_range": { "start": 0, "end": 300000 }
}
]
}
]
}
}
  • The photo’s visual.opacity and visual.scale are keyed in timeline time: the fade-in runs from 0 to 0.5 s of the project and the slow zoom over the whole four seconds.
  • The blur’s radius in the focus composition is keyed in composition time: it clears over the clip’s first second.
  • The dot’s center is keyed in picture time of the dot source, from 0 to 2 s. The clip puts that picture on the timeline at 1 s, so the dot starts moving at 1 s and stops at 3 s.

The shapes preset keys an ellipse’s centre in picture time, so it moves across the three seconds of its clip: