Loops
Make a film that repeats with no jump and no stall, where the last frame leads into the first frame.
At the end of this page you have a filmA film is the mp4 file that the renderer makes from one page. whose last frame leads into its first frame like any other frame step. When a player repeats the film, the viewer sees no seam.
The template for a loop is prompts/ui-morph-loop.md.
What this film needs
A loop is one element that becomes eight to twelve UI states and returns to the first state, on a beat grid, with a cursor that drives every change (ui-morph-loop.md). The length is 12 to 16 s. Seven bars at 120 BPM is 14 s: each bar is 2 s and each beat is 0.5 s.
A loop has no ending, so it has no answer. The hook-payoff rule lets a loop break its payoff rule if the brief says so. Write that line in the brief.
The element is never cut. Each state is the same element that morphs its size, radius and colour, while its content swaps with a short blur. Something happens on every beat. The camera zooms so each state fills the frame, and the zoom is a transform on one wrapper.
The spectacleThe spectacle is the second of the one big moment in a film, set by <meta name="spectacle" content="6.2">. still applies: choose one state as the big moment, and give it quiet before it.
Steps
Write the brief with the morph template
bin/vawe new morph --from prompts/ui-morph-loop.md --length 14 --aspect 1:1The template asks for the 8 to 12 states, the accent (black and white by default), a song at about 120 BPM or no music, the aspect and length, and the UI sans. The default is 1:1 and 14 s. Read Write the brief. Write the state list on the beat grid before any code: state, beat, what the cursor does, what the sound is.
Build every style from time
Every style is computed from t inside window.seek(t). Use no CSS transitions and no timers, and carry no state between frames. A value that changes target many times is the sum of one spring per change, so it stays a pure function of time. Use spring(t, k, d) and track(t, keys, k, d) from core/motion/springs.js.
For a periodic part, loopT(t, dur) in the same file wraps t into [0, dur), also for negative t.
Make the page at the duration equal the page at 0 s
Give each value at t = duration the value it has at 0 s, including the position and the speed of the cursor. The renderer never paints t = duration: the last frame is at duration - 1/fps. So the last frame is one ordinary frame step before the first frame.
Do not make the last frame identical to the first frame. Then the player shows that picture twice, and the loop stalls for one frame. A spring that is still moving at the duration shows as a jump at the seam.
Test the seam
bin/vawe critique films/morph/page.htmlThe critique writes the loop seam: the last 0.5 s and the first 0.5 s on one sheet. It compares the last-to-first pixel difference with the usual difference between two adjacent frames of the last 0.5 s. It prints one of three verdicts:
seam reads like an ordinary frame step: the loop is good.jump: the seam moves more than 3 times an ordinary frame step. A value at the duration is not its value at 0 s.doubled frame: the last frame equals the first, so the loop stalls for one frame. Let the values reach their 0 s values at the duration, one frame step later.
Do not test the seam with still --at 0 and still --at 14. still moves a time past the last frame to the last frame, so --at 14 gives the frame at 14 - 1/fps. On a good loop, that frame differs from the first frame by one frame step. Read Critique and fix.
Make a small file for sharing
bin/vawe dev films/morph/page.html --small--small encodes the draft about 5 times smaller, for a file to post such as an og:video share loop.
Pitfalls
- Do not fake the blur. The renderer applies motion blurMotion blur is the streak of a fast move, made in the final by blending up to 32 subframes for each frame. from subframes on its own.
- A swap is its own problem. A morph between two states with different content is a shape tween plus a content swap. Give the swap its own 120 ms and blur it, and let the shape carry the eye.
- Zoom the wrapper, never the element, or its springs fight the camera.
- No
will-changeon anything the camera scales, or the text renders blurry. - Text that swaps in a morphing container needs its own enter and exit timing, or it overlaps.
- The
loopattribute is for the bed. On an<audio>tag,loopmarks the music bedA bed is a music track that plays under the whole film, marked by the loop attribute on an <audio> tag. under the film. It does not make the film repeat.
From the taste digest, three rules matter most. Keep the accent to one thing in each frame (accent-share). Keep a hold alive with a part that moves (live-hold). Exits run shorter than entrances (exits-shorter).
Common mistakes
| What you see | Why | What to do |
|---|---|---|
The loop seam says jump | The cursor or a spring at the duration has not come back to its value at 0 s | Give each value at the duration its value at 0 s, with the same speed |
The loop seam says doubled frame | The last frame equals the first frame | End the values at the duration, not at the last frame |
| The seam sheet shows one part that changes at the cut | A state keeps a part that the first state lacks | Return that part to its first state |
| The text is blurry after the zoom | will-change on a scaled element | Remove it |