Command Palette

Search for a command to run...

Hectal
Course outline
Topic 9.1Intermediate ~21 min · step 1 of 6

Time-Based Animation

In one line

Animate by how much time has passed, not by how many frames have been drawn. A wheel then turns at omega = v / r radians a second on a 30 fps phone and on a 144 Hz monitor alike.

0/6 · 0%

Think of it like this

Two friends walk to school. One takes "ten steps a second", the other "five". If the rule is "ten steps, then stop", the first arrives sooner. If the rule is "walk at one metre a second", both arrive together, however fast their feet go. Frames are steps. Time is the clock on the wall.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

delta time
The seconds since the previous frame. Multiply every speed by it so motion does not depend on the frame rate.
angular speed (ω)
How fast something turns, in radians per second. One turn is 2π radians (about 6.283).
rolling radius
The distance from the axle to the ground. The wheel moves one circumference (2π r) for each turn.
frame rate (fps)
How many pictures are drawn a second. Phones often draw 30 or 60, monitors 60 to 240.
aliasing (wagon wheel)
A fast, repeating motion sampled too slowly looks slow, stopped or reversed.

Step by step

01Pick the units first

Write speeds with their units: road speed in m/s, wheel speed in rad/s. A speed in "degrees per frame" has no meaning on another machine.

02Work out omega

20 km/h is 20 / 3.6 = 5.556 m/s. Divide by the radius 0.352 m: 15.78 rad/s. Divide by 2π for turns: 2.51 a second. One turn rolls the bike 2π × 0.352 = 2.212 m.

03Multiply by delta

Each frame: wheel.rotation.z -= omega * delta. The minus makes the top of the wheel move forward (towards +x), which is a clockwise turn when you look from +z.

04Test the edge: a long gap

The third example feeds the loop a 12 s gap, as a hidden tab would. Without the clamp the wheel turns 30 times in one frame. With it, a quarter turn.

Try it yourself

  1. 1

    Change the speed

    In the first example set SPEED_KMH to 5, then 60. The log prints 1.389 m/s and 3.95 rad/s (0.63 turns a second) for 5 km/h, and 16.667 m/s and 47.35 rad/s (7.54 turns a second) for 60 km/h. At 60 the orange spoke looks stuck or reversed: that is the wagon-wheel effect from the second example.

  2. 2

    Use a slower clamp

    In the third example set MAX_DELTA to 0.05. Frames 1 to 3 are 0.016 s, so they do not change. Frame 4 now turns the wheel 0.13 turns instead of 0.25.

Code & diagrams

A wheel at a real speed New tab

Look at the status bar: the fps changes with your machine, the wheel's speed does not. Change `SPEED_KMH` to 5, 60 or 0.

idle

Console

Run the scene to see its console.

Expected console output

road speed: 5.556 m/s
wheel speed: 15.78 rad/s = 2.51 turns/s
one turn rolls the bike: 2.212 m
The wagon-wheel effect, as a table New tab

A 12-spoke wheel looks identical every 30 degrees. This computes what you see, and nothing is drawn.

idle

Console

Run the scene to see its console.

Expected console output

km/h  fps  degrees/frame  looks like
   5   30            7.5   forwards 7.5 deg
   5   60            3.8   forwards 3.8 deg
   5  144            1.6   forwards 1.6 deg
  15   30           22.6   BACKWARDS 7.4 deg
  15   60           11.3   forwards 11.3 deg
  15  144            4.7   forwards 4.7 deg
  40   30           60.3   standing still
  40   60           30.1   standing still
  40  144           12.6   forwards 12.6 deg
 100   30          150.7   forwards 0.7 deg
 100   60           75.4   BACKWARDS 14.6 deg
 100  144           31.4   forwards 1.4 deg
A tab hidden for 12 seconds New tab

Plain numbers, no canvas. Frame 4 arrives after a 12 s gap. Without the clamp the wheel would jump 30 turns.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

frame  delta s  turns (raw)  turns (clamped)
    0    0.000         0.00             0.00
    1    0.016         0.04             0.04
    2    0.017         0.04             0.04
    3    0.016         0.04             0.04
    4   12.000        30.14             0.25
    5    0.017         0.04             0.04
total turns: 30.30 raw, 0.42 clamped

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Per-frame motion

In the first example, replace radiansPerSecond * timer.getDelta() with 0.1.

terminal
$ Run
── what you'll see ──
(no console error)
The wheel turns 0.1 radians per frame: 6 rad/s at 60 fps, 14.4 rad/s at 144 fps, 3 rad/s at 30 fps. The log still prints 15.78 rad/s but is no longer true.

Break #2

Forgetting the clamp

In a loop of your own, add delta straight to the angle with no Math.min.

terminal
$ Run, then pause the page in the debugger for 5 s and resume
── what you'll see ──
(no console error)
The wheel lurches many turns in one frame, then carries on.

Myth vs fact

Myth

requestAnimationFrame always runs at 60 fps.

Fact

It runs once per screen refresh: 30, 60, 90, 120, 144 or 240 a second, and it slows when the page is busy. It pauses completely in a hidden tab.

Myth

A wheel that looks backwards is turning backwards.

Fact

It is turning forwards. The eye sees only one picture per frame and picks the smallest turn that explains it.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Fixed time steps are the alternative to variable delta. A physics engine (Phase 16) steps in fixed slices (for example 1/60 s) and runs as many as the real time needs. This keeps results repeatable. Animation that only looks right does not need it.

  • ▸

    Adding delta to an angle that grows for hours loses float precision slowly. For a wheel it does not matter. For a long clock, keep the angle modulo 2π: angle = (angle + step) % (2 * Math.PI).

  • ▸

    The three.js Timer replaced Clock (deprecated since r183) because Clock read performance.now() itself and so could not be driven by the timestamp requestAnimationFrame already gives you. Passing time into timer.update(time) keeps every system on the same frame time.

Remember this

  1. 1

    The problem this solves: wheel.rotation.z -= 0.1 inside the loop turns the wheel 0.1 radians per frame. At 144 frames a second that is 14.4 rad/s. At 30 frames a second it is 3 rad/s. The speed depends on the screen, and so does everything built on it.

  2. 2

    Delta time is the seconds since the previous frame. Multiply every speed by it: angle += speed * delta, with speed in radians per second. You met THREE.Timer in Topic 3.2. timer.update(time) then timer.getDelta() gives the delta, and with timer.connect(document) it is 0 while the tab is hidden.

  3. 3

    How fast should a wheel turn? A wheel that rolls without slipping moves the bike one circumference (2π r) per turn. At road speed v metres per second the wheel turns at ω = v / r radians a second. Our tyre's rolling radius is 0.33 m (rim) + 0.022 m (tyre) = 0.352 m. At 20 km/h (5.556 m/s) that is 15.78 rad/s, or 2.51 turns a second.

  4. 4

    The wagon-wheel effect. A screen shows still pictures. Our wheel looks the same after every 30° (12 spokes), so a turn of 30° per frame looks like no movement, and 45° looks like 15° the wrong way. This is not a bug in the code. It is sampling, the same reason a real wheel on film can look slow or reversed. At 144 fps the turn per frame is small, so the wheel looks right.

  5. 5

    Clamp the delta. delta is large after a stall: a breakpoint, a heavy garbage collection, a slow page. If the tab was hidden for 12 seconds and the code adds 12 s of motion in one frame, a wheel jumps 30 turns. Cap it: Math.min(delta, 0.1). The world then slows down on a very slow device instead of lurching. Our loop.js does this in the build.

  6. 6

    Never log time. The examples below print values computed from fixed steps (1 / 60), because real clocks differ on every run. That is also how you test animation code: call the update with a fixed delta and check the result.

Explain it without notes

01

Why does rotation.z += 0.1 give different speeds on different screens, and how do you fix it?

02

A bike rolls at 18 km/h on wheels with a 0.35 m rolling radius. How fast should the wheel turn, in radians and in turns a second?

Practice

01

Write wheelStep(speedKmh, radius, delta) that returns the radians to add this frame. What does it return for 36 km/h, radius 0.35 and delta 1/60?

02

A tab is hidden for 30 seconds and shown again. With Timer.connect(document) and a clamp of 0.1, how far can the wheel jump on the first frame?

Trade-offs

  • ↔

    A clamp keeps the world calm after a stall but makes the world run slow on a device that is always below 10 fps. Pick the clamp for the experience you want on a bad day.

Done when you can

  • Explain why per-frame motion depends on the screen

  • Work out omega from a road speed and a wheel radius

  • Clamp the delta and say what it protects against

  • Say why a fast wheel can look slow or reversed