Timing and composition
This page covers how elements are timed against each other, and how to build an indicator from several groups.
Element time
Every element has a start offset (from stagger) and a cycle length (from durations, the part duration or the spec duration). At time t:
local = t - offset.- If
localis negative, the element has not started and shows its rest values. - Otherwise the cycle progress is
(local mod cycle) / cycle, from 0 to 1, and every track is sampled at that point.
Stagger
stagger delays elements so they move one after another. It has two forms.
{ "each": 0.1, "start": 0 } gives element i the offset start + each × i seconds. start is optional.
{
"$schema": "https://maitrungduc1410.github.io/loader-kit/schema/v1.json",
"schemaVersion": 1,
"name": "TimingWave",
"duration": 1.2,
"layout": { "type": "row", "count": 5, "gap": 0.06 },
"shape": { "type": "circle" },
"stagger": { "each": 0.1 },
"tracks": [
{
"property": "translateY",
"keyTimes": [0, 0.25, 0.5, 1],
"values": [0, -0.2, 0, 0],
"easing": ["easeOut", "easeIn", "linear"]
}
]
}TimingWaveOpen in playground - Element 0, starts at 0 s
- Element 1, starts at 0.1 s
- Element 2, starts at 0.2 s
- Element 3, starts at 0.3 s
- Element 4, starts at 0.4 s
An array gives each element its own offset, by index. It must have at least one entry per element. The grid example uses an array to start a wave from a corner.
Negative offsets
Offsets can be negative. An element with a negative offset is already part way through its cycle at t = 0. Use this when every element should be visible from the first frame, as in the stack ripple: [0, -0.5, -1] with a 1.5 second cycle spreads three circles evenly.
Choosing offsets
For an even spread, use offsets of cycle / count. For a ring of 8 elements and a 1 second cycle, each: 0.125 makes one full wave per cycle.
Rest values
Before an element starts, and for every property without a track, the element shows its rest values: scale, scaleX, scaleY, opacity and strokeEnd are 1, every other property is 0.
rest changes them for a group. Here rest: { "scale": 0 } hides each dot until its stagger delay has passed, so the dots appear one by one at the start:
{
"$schema": "https://maitrungduc1410.github.io/loader-kit/schema/v1.json",
"schemaVersion": 1,
"name": "TimingRest",
"duration": 1.2,
"layout": { "type": "row", "count": 4, "gap": 0.08 },
"shape": { "type": "circle" },
"stagger": { "each": 0.3 },
"rest": { "scale": 0 },
"tracks": [
{ "property": "scale", "keyTimes": [0, 0.5, 1], "values": [0, 1, 0], "easing": "easeInOut" }
]
}TimingRestOpen in playground rest also places elements: a property without a track keeps its rest value forever. The parts example uses rest.translateY to put a dot above the center.
Frozen frames skip the wait
When a view freezes a frame with cycleProgress, the engine skips whole cycles until every element has started. So a frozen frame never shows elements at their start-up rest values.
Durations
durations gives each element its own cycle length, by index. It overrides the part and spec duration, and must have at least one entry per element. Elements with different cycles drift in and out of phase:
{
"$schema": "https://maitrungduc1410.github.io/loader-kit/schema/v1.json",
"schemaVersion": 1,
"name": "TimingDurations",
"duration": 1,
"layout": { "type": "row", "count": 3, "gap": 0.1 },
"shape": { "type": "circle" },
"durations": [0.8, 1, 1.3],
"tracks": [
{ "property": "scale", "keyTimes": [0, 0.5, 1], "values": [1, 0.3, 1], "easing": "easeInOut" }
]
}TimingDurationsOpen in playground Parts
A spec is one group of elements, written inline. To combine groups with different layouts, shapes or tracks, list them in parts. Each part accepts layout, shape, tracks, stagger, duration, durations, rest and groupTracks.
- Parts are drawn in order, so later parts are on top.
- Element indices continue across parts: the first element of part 1 follows the last element of part 0. The
colorssetting uses these indices. - A part's
durationsets its own cycle length. The default is the specduration. - A spec with
partsmust not set the group fields at the top level.
Here part 0 is a large pulsing circle. Part 1 is a small dot placed above it with rest, blinking on a cycle twice as fast:
{
"$schema": "https://maitrungduc1410.github.io/loader-kit/schema/v1.json",
"schemaVersion": 1,
"name": "TimingParts",
"duration": 1.2,
"parts": [
{
"layout": { "type": "single", "size": 0.4 },
"shape": { "type": "circle" },
"tracks": [
{ "property": "scale", "keyTimes": [0, 0.5, 1], "values": [1, 0.7, 1], "easing": "easeInOut" }
]
},
{
"layout": { "type": "single", "size": 0.16 },
"shape": { "type": "circle" },
"duration": 0.6,
"rest": { "translateY": -0.4 },
"tracks": [
{ "property": "opacity", "keyTimes": [0, 0.5, 1], "values": [1, 0.4, 1] }
]
}
]
}TimingPartsOpen in playground Group tracks
groupTracks animate a whole group around the center of the box. They support scale, scaleX, scaleY, opacity, rotate, translateX and translateY. They run on the group's cycle, start at t = 0 and ignore stagger. The group's rest does not apply to them.
Replace the blink of the small dot with a group rotation, and it orbits the center:
{
"$schema": "https://maitrungduc1410.github.io/loader-kit/schema/v1.json",
"schemaVersion": 1,
"name": "TimingGroupTracks",
"duration": 1.2,
"parts": [
{
"layout": { "type": "single", "size": 0.4 },
"shape": { "type": "circle" },
"tracks": [
{ "property": "scale", "keyTimes": [0, 0.5, 1], "values": [1, 0.7, 1], "easing": "easeInOut" }
]
},
{
"layout": { "type": "single", "size": 0.16 },
"shape": { "type": "circle" },
"rest": { "translateY": -0.4 },
"groupTracks": [
{ "property": "rotate", "keyTimes": [0, 1], "values": [0, 6.283185307] }
]
}
]
}TimingGroupTracksOpen in playground A group track is applied after the element transform. So element tracks move elements inside the group, and group tracks move the whole group.
Perspective
rotateX and rotateY flip elements in 3D. perspective is the distance from the viewer to the box, in box units (default 2.5). A smaller value makes the 3D effect stronger. It is set once for the whole spec.
{
"$schema": "https://maitrungduc1410.github.io/loader-kit/schema/v1.json",
"schemaVersion": 1,
"name": "TimingPerspective",
"duration": 2,
"perspective": 1.5,
"layout": { "type": "single", "size": 0.6 },
"shape": { "type": "rect", "cornerRadius": 0.1 },
"tracks": [
{ "property": "rotateX", "keyTimes": [0, 0.5, 1], "values": [0, 3.141592654, 3.141592654], "easing": "easeInOut" },
{ "property": "rotateY", "keyTimes": [0, 0.5, 1], "values": [0, 0, 3.141592654], "easing": "easeInOut" }
]
}TimingPerspectiveOpen in playground The built-in SquareSpin uses the same idea with a perspective of 2.5.
Transform order
For each point of a shape, engines apply, in order: scale, rotateX, rotateY, rotate, perspective, translation to the element position, then the group transform. The specification gives the matrices.