Skip to content

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:

  1. local = t - offset.
  2. If local is negative, the element has not started and shows its rest values.
  3. 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.

json
{
  "$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"]
    }
  ]
}
null
0.00
0.02-0.2200.61.2time (s)
  • 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:

json
{
  "$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" }
  ]
}
null

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:

json
{
  "$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
null

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 colors setting uses these indices.
  • A part's duration sets its own cycle length. The default is the spec duration.
  • A spec with parts must 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:

json
{
  "$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] }
      ]
    }
  ]
}
null

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:

json
{
  "$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
null

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.

json
{
  "$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
null

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.

Released under the MIT License.