Tracks and keyframes
Each track in a clip moves one property of one target through its keyframes. This page shows how the engine reads them: their order, the holds, the clip's length and a track's own cycle.
A track per property
Each track moves one property of one target. A target that moves in several ways gets a track for each: this ball has x, y and fill, all on the target "ball".
Under the scene is a lane per track, drawn from what compile made of it. Dots are keyframes, solid lines are where it runs from one key to the next, dashed lines are where it holds.
{
"duration": 3,
"tracks": [
{
"targetUid": "ball",
"property": "x",
"keyframes": [
{ "time": 0.3, "value": 0 },
{ "time": 2.4, "value": 440 }
]
},
{
"targetUid": "ball",
"property": "y",
"keyframes": [
{ "time": 1, "value": 0, "easing": "power2.out" },
{ "time": 1.35, "value": -56, "easing": "power2.in" },
{ "time": 1.7, "value": 0 }
]
},
{
"targetUid": "ball",
"property": "fill",
"keyframes": [
{ "time": 0.3, "value": "#8b5cf6" },
{ "time": 1.35, "value": "#fd0ddd" },
{ "time": 1.35, "value": "#ff8f00" }
]
}
]
}Keyframes
Keyframes can come in any order: the engine sorts them by time. Keys at the same moment keep the order they were written in and make a jump: from that moment on, the later one counts. That is how the ball's color jumps from pink to orange at the top of its hop.
Before its first key- Holds the first key's value.
Between two keys- Runs from one to the next, along the ease of the key it leaves.
Two keys at one moment- A jump: from that moment on, the one written later counts (when they are the track's first keys, from just after it).
After its last key- Holds the last key's value until the lap is up, or until its
cyclestarts over. Two tracks on one property of one target- They don't add up: one shows at a time. While stretches run, the one whose stretch started last; once they end, the one that ended last; before any starts, the one written last. The one written last plays throughout when one of them has a
cycle, and always formotion,morph,warp,fillGradientandanchor(amorphand awarpon one target count as one here).
How long a clip runs
A clip runs for its duration, and never less than its last keyframe: pick a duration of 1 s above and it still runs 2.4 seconds, as program.duration says. A track that ends sooner holds its last value until the lap is up.
A duration of Infinity never ends. Stew Factory uses that for a shape's loop actions, where each track keeps its own cycle. JSON has no Infinity, so set it in code.
A track's own cycle
A track with a cycle starts its keys over every cycle seconds, for as long as the clip runs. Here the hop's three keys last half a second, while the walk crosses the whole stage. The smaller dots are the hop's keys again, every cycle.
Turn the cycle off and the hop plays once a lap, then holds. Make it longer than the keys and the hop rests until the next cycle. Keys later than cycle are never reached, and the cycle starts over with each lap.
{
"duration": 4,
"tracks": [
{
"targetUid": "hopper",
"property": "x",
"keyframes": [
{ "time": 0.25, "value": 0, "easing": "none" },
{ "time": 3.75, "value": 470 }
]
},
{
"targetUid": "hopper",
"property": "y",
"cycle": 0.5,
"keyframes": [
{ "time": 0, "value": 0, "easing": "power2.out" },
{ "time": 0.25, "value": -24, "easing": "power2.in" },
{ "time": 0.5, "value": 0 }
]
}
]
}cyclenumberoptional- Seconds: the track's keys start over every
cycleseconds, whatever the clip's length. Anything but a finite number above 0 means no cycle.
Values
A keyframe's value is a number or a string, read the way its property needs.
Numbersnumberx,rotate,opacity,draw,motionand the other properties the engine plays that aren't below (Getting started lists them). A string that reads as a number, like"12", counts too.Colorsstringfill,strokeandshadowColor:#rgb,#rgba,#rrggbb,#rrggbbaa,rgb(),rgba(),hsl()orhsla()(with commas or spaces), one of the 148 CSS color names, ortransparent. Colors blend straight in RGB."none"string- A
fillorstrokewith no paint. Going to it, the paint goes off right after the key before, so the stretch toward"none"shows nothing (never a fade). Coming out of it, the paint is back right after the"none"key, and the color blends in from transparent black. Outlinesstringmorphandwarp: SVG path data, a whole outline per keyframe.GradientsstringfillGradient: the gradient as JSON text, with itstype("linear"or"radial"), a linear one'sanglein degrees (default 90, top to bottom) and itsstops, each with anoffset, acolorand anopacity(default 1). Every keyframe needs the first one's type and number of stops.Pivotsstringanchor: two fractions of the target's box, like"0 1"for its left bottom. The target needs an entry inboxes, and the anchor only plays where the same target also has a move, turn, scale or skew track (x,rotate,scale,skewX...).
What compile leaves out
What compile can't read, or doesn't play yet, is left out with a note in program.skipped, never thrown, and the rest plays as written. Add one flaw at a time and watch the note, the lane and the ball. A key it left out shows hollow; a track it left out, faint.
Each note starts with the track's target and property, like ball wobble: not played yet. The ease's note names the ease instead.
import { compile } from "@stew/engine";
import clip from "./clip.json";
// read once: what it can't play is left out, never thrown
const program = compile(clip);
program.duration; // 3
program.skipped; // [] (it plays everything)not played yet- A
propertythe engine doesn't play. The whole track is left out. left out 1 keyframe(s) it can't read- A keyframe whose
timeisn't a finite number, or whosevaluedoesn't read. The track plays without it; with no keyframe left, the track is left out too. left out 1 keyframe(s) that aren't path data- A
morphorwarpkeyframe whosevalueisn't path data. The track plays without it; with none left, it is left out too. (One whosetimeisn't a finite number is dropped with no note.) the ease "...": unknown, Stew's default (power1.inOut) instead- An
easingit doesn't know, on a key with a key after it. That stretch playspower1.inOutinstead. One note per name; the last key'seasingleads nowhere and isn't read. no path to follow- A
motiontrack whose target has no entry inpaths. The whole track is left out. its path draws nothing- A
motiontrack whose path'sddraws nothing. The whole track is left out. its frame flattens everything- A
motiontrack whose path'sframesquashes everything flat, so it can't be undone. The whole track is left out. no box to find its pivots in- An
anchortrack whose target has no entry inboxes. The whole track is left out. its first keyframe holds no gradient it can read- A
fillGradienttrack whose first keyframe isn't a gradient it can read. The whole track is left out. 1 keyframe(s) of another kind of gradient (type or stop count) hold the one before- A later
fillGradientkeyframe that isn't a gradient it can read, or has anothertypeor number of stops than the first. It holds the gradient before it.
durationnumber- Seconds: the clip's
duration, never less than its last keyframe, orInfinity. loopboolean- The clip's
loop:trueunless it saysfalse. The player doesn't read it: it repeats by its ownrepeatoption. skippedstring[]- What it left out and why, one note each. Empty when it plays everything.