Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 9.4Intermediate ~28 min · shot 4 of 7

Interruption and Velocity Hand-Off

In one line

When you send a moving spring somewhere new, it keeps its position and its speed and turns round smoothly. A CSS transition begins a new curve with a speed of its own.

0/7 · 0%

Think of it like this

A runner chasing a bus. If the bus turns a corner, the runner does not stand still, think, and set off again. They change direction while still moving. A spring is that runner; a restarted animation is one that stops at every change of mind.

Words you'll meet

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

retarget
Change a running spring's target while keeping its position and speed.
velocity hand-off
Starting the next motion at the speed the last one had, so there is no jolt.
interruption
The user changing their mind while an animation is still running.
restart
Stopping an animation and starting a new one from rest, which loses the speed.

Step by step

01Slow it down and toggle

In "Hand-off" press 0.1×, Toggle, then Toggle again about 150 ms in. The blue ball reverses in one frame; the orange one slows, stops and returns.

02Read the numbers

At the turn the transition ball is at 122.2 px, one frame later 103.6. The spring is at 94.8, then 97.8, 96.9, 91.1.

03Chase the pointer

Move the mouse over the pad. The orange dot (one retargeted spring) stays close; the blue one (a new spring per move) lags.

04Toggle 3 times quickly

Both balls end at 160. The spring turned round twice from its own speed; the transition started a new curve at each click.

Try it yourself

  1. 1

    Change the transition's easing

    In "Hand-off" add --e: ease-in-out; to .t. Toggle, then toggle again at 150 ms: the ball is only at 20.6 px and the reversing transition lasts just 77 ms (the browser shortens a reversal in proportion to how far the first one got). Its steps back are -1.8, -6.0, -7.5, -4.5 px per frame: a new curve again.

  2. 2

    Stiffen the chase

    In "Chasing a moving target" use stiffness: 1200, damping: 60: the retargeted spring ends 24 px behind (the restarted one 85 px). A spring trails a steadily moving target by about c × speed ÷ k, and 60 ÷ 1200 is half of 30 ÷ 300.

Code & diagrams

Try it: interactive

Go and Interrupt

Press Go, then Interrupt halfway. The spring turns round with its speed. The tween restarts from rest.

Hand-off

Top: a CSS transition (600 ms, ease-out) that you reverse. Bottom: a spring that you retarget. The console runs the real spring on a controlled clock and prints its speed before and after the turn.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

turned round at x 85, speed before 630 px/s, after retarget 630 px/s
a new spring from the same place starts at speed 0 px/s
Chasing a moving target

Move the pointer sideways over the dashed pad. Orange: one spring, retargeted. Blue: a new spring on every move. The console replays a pointer that moves 10 px a frame.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

one spring, retargeted: the follower ends 54 px behind the pointer
new spring each move: the follower ends 266 px behind the pointer

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

Restart the spring on every click

In "Hand-off" replace if (motion) return motion.retarget(to); with motion?.stop(); motion = null;.

terminal
$ Toggle, toggle again at 150 ms, probe 150, 166, 183, 200 ms
── what you'll see ──
The orange ball is at 92.9, 91.4, 86.8 and 78.6 px: it turns back at once (-1.5, -4.6, -8.1 px per frame), building up from a standstill. The retargeted spring first kept going (+3.0), then curved round (-0.8, -5.9).

Myth vs fact

Myth

A CSS transition already handles interruption, because it starts from the current value.

Fact

It continues the position, not the speed. The new curve begins at its own speed: here +7.6 became -18.7 px per frame.

Pro corner

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

  • ▸

    Motion hands velocity over for physics-based springs and ignores it for duration-based ones. For a drawer that can be thrown, spell out stiffness and damping.

Remember this

  1. 1

    The problem this solves: users change their minds mid-animation: they tap the cart twice, hover out early, drag a card. The motion must answer at once and look continuous, because people should never have to wait for an animation to finish before the next tap counts.

  2. 2

    **What retarget keeps.** spring.js holds a position and a speed, and retarget(to) only changes the target. In the first example a spring turned round at x 85 with 630 px/s before and 630 px/s right after: the pull flips and the speed changes smoothly, with no jump.

  3. 3

    What a CSS transition does. Reverse the class and the browser starts a new transition from the current value with its own curve (here shortened from 600 to 458 ms). Ease-out starts at full speed, so the ball moved +7.6 px a frame, then -18.7 on the next: a jump. The spring's steps were +12.0, +3.0, -0.8, -5.9.

  4. 4

    A restarted spring is no better. A new spring from the same position starts at speed 0. A pointer that moved 10 px per frame for 60 frames left one retargeted spring 54 px behind, but a new spring on every move 266 px behind: each one was thrown away before it built any speed.

  5. 5

    Rapid toggling and moving targets. Toggle 3 times quickly (clicks at 0, 110 and 220 ms): the spring turns round twice from its own speed, and both balls end at 160. Following the pointer is retarget on every pointermove: cheap, since the loop already runs.

  6. 6

    What does not carry over. Duration-based springs (9.3) and CSS linear() springs (9.5) cannot take an incoming speed. Only a spring simulated step by step can.

Explain it without notes

01

What does a spring keep when you retarget it, and what does a CSS transition keep?

02

Why does a new spring on every pointer move make the follower lag?

Practice

01

Build a new spring from an old one without losing its speed.

02

How should a pointer-follower behave when the pointer leaves the pad?

Done when you can

  • You can say what retarget keeps

  • You can explain the speed jump of a reversed transition

  • You can follow a moving target with one spring