Shots in this scene
Playback Control
In one line
An Animation is a small video player: play(), pause(), reverse(), finish() and cancel() steer it, currentTime seeks it and playbackRate changes its speed. Each has an edge case that the remote below lets you hit on purpose.
Think of it like this
A video player. You can play, pause, drag the slider to any moment, watch at double speed, or run it backwards, and the video never has to be re-made. An Animation object is that player for motion.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- playState
- idle, running, paused or finished: where the Animation is in its life.
- currentTime
- How far the animation is along its own clock, in ms, including the delay. null when idle.
- playbackRate
- The speed multiplier: 1 normal, 0.5 half speed, negative backwards, 0 frozen.
- seek
- Jump to a time by setting currentTime.
- pending
- True for about one frame after you play, pause or change the rate, until the browser has applied it.
- idle
- An animation with no current time: never played, or cancelled.
Step by step
01Play, pause and read the state line
In "A remote for one animation", press Play, then Pause. The line under the buttons shows what the Animation itself reports: running | currentTime 812 | rate 1, then paused. The track on the timeline stays while paused: the animation still exists.
02Drag the scrubber
Drag the slider. The handler calls pause() and then sets currentTime. The dot follows your hand because every currentTime maps to one position. Press Play: it continues from where you left it.
03Reverse in the middle, then at the start
Press Play, and Reverse while the dot is moving: it turns round at once and runs back (a real run reversed at 500 ms and the dot went back down the same curve). Run the example again and press Reverse before Play: the dot jumps to the far end and runs back to the start. That is the auto-rewind rule. Press Reverse once more and the sign flips again, so the dot runs forward.
04Finish, then cancel
Finish puts the dot at the end (finished). Cancel sends it back to its own place (idle | currentTime null) and the track disappears: the animation was removed.
Try it yourself
- 1
Seek, then play
In "A remote for one animation", pause, drag the slider to 1500, then press Play. In a real run the dot sat at 191.6 px (the eased position at 75% of the time, not 75% of the distance), moved on and arrived at 220 px 500 ms later. A seek does not change the easing: it only chooses the moment.
- 2
Half speed
Choose rate 0.5 in the select, then press Play. The 2000 ms animation takes 4000 ms: in a real run the dot was at 28.4 px after 1000 ms, at 110 px (half way) after 2000 ms, and finished at 4000 ms. The track's duration still says 2000 ms: the rate changes how fast time passes, not the keyframes.
- 3
Cancel versus finish
Press Play, then Cancel: the dot jumps back to its own place at once and the track vanishes. Press Play and Finish: the dot jumps to the end and stays there, because the animation has
fill: "both". Removefill: "both"and Finish snaps back at the next frame.
Code & diagrams
One animation with fill: both so the dot stays at both ends. The buttons call the five methods, the slider seeks, and the select calls updatePlaybackRate(). The line under it prints the animation's own state every frame.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Chromium 153. Each line is one edge case, read straight from the Animation object.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
finish(): finished, currentTime 1000
reverse() on a finished animation, right away: running, currentTime 1000, playbackRate 1, pending true
...and once it is ready: running, playbackRate -1
reverse() while paused at the start: currentTime 1000
playbackRate 0 after 3 frames: running, currentTime 0
currentTime 5000 on a 1000 ms animation: finished, currentTime 5000
finish() on an endless animation: InvalidStateError: Failed to execute 'finish' on 'Animation': Cannot finish Animation with an infinite target effect end.
currentTime = null while running: TypeError: Failed to set the 'currentTime' property on 'Animation': currentTime may not be changed from resolved to unresolved
right away, updatePlaybackRate(2) vs playbackRate = 2: 1 vs 2
once ready: 2 vs 2Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Set the rate to 0 and wait
In "A remote for one animation", add the line anim.playbackRate = 0; after anim.pause();.
Myth vs fact
Myth
cancel() ends an animation like finish() does.
Fact
finish() runs it to the end (and a fill keeps the end). cancel() removes it: the element goes back to its own style, currentTime becomes null, and anything waiting on finished is rejected (Topic 8.5).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The animation's
startTimeis the moment ondocument.timelineit started. While pending it isnull; afterpause()it isnullandcurrentTimeholds the position. ChangingcurrentTimewhile running recomputesstartTime. - ▸
animation.timelinecan be aScrollTimelineinstead ofdocument.timeline: thencurrentTimemeans scroll position, and the same methods work (Phase 12).
Remember this
- 1
The problem this solves: the drawer must close when the user changes their mind half-way through opening it. If all you can do is cancel and start a new animation, the new one begins from the start and the drawer jumps. With a handle you call
reverse()and the drawer turns round from where it is. The same handle drives demos, scrubbers and slow motion. - 2
The four states.
playStateisidle(never played, or cancelled),running,pausedorfinished. A newly started animation is alsopendingfor one frame, until the browser has started it. - 3
The five methods.
play()starts or resumes, and fromfinishedrestarts at the beginning.pause()freezes in place.finish()jumps to the end (the start, when playing backwards).cancel()throws the animation away:currentTimebecomesnull, the element returns to its own style, the animation goes idle and acancelevent fires.reverse()flips the sign ofplaybackRate(2 becomes -2) and plays. - 4
**Where
reverse()surprises.** It isplaybackRate = -1plusplay(), with an extra rule: if the animation is already at the end it plays back from there, and if it is at the *start* it first jumps to the end. In the console below, a paused animation atcurrentTime0 reportscurrentTime 1000right afterreverse(). - 5
**
currentTime.** Milliseconds on the animation's own clock, including anydelay. You can set it:anim.currentTime = 500seeks. Past the end the animation isfinishedand keeps the number you gave (5000 on a 1000 ms animation). A negative number is before the start: the animation ispausedorrunningthere but shows nothing without a backwards fill. You cannot set it tonullwhile it is running: that throws aTypeError. To seek a running animation,pause()first, or the clock keeps moving under your hand. - 6
**
playbackRate. 1 is normal, 2 is double, 0.5 is half, a negative number plays backward. A rate of 0 is a trap:**playStatestaysrunningandcurrentTimenever moves, so nothing looks wrong in the state and nothing moves on screen. SettingplaybackRatetakes effect at once.updatePlaybackRate(rate)first synchronises the position and applies the change on the next frame, so there is no jump when the animation is running off the main thread (MDN).playbackRatestill shows the old value untilreadyresolves. - 7
Keyboard and accessibility. A scrubber should be a real
<input type="range">, as in the remote below: arrow keys and screen readers work. Motion that plays on its own for over five seconds needs a visible pause control (WCAG 2.2.2, Topic 7.4).
Explain it without notes
How do you make a drawer turn round half-way, without a jump?
What is the difference between finish() and cancel()?
Why can a running animation not move?
Practice
Write toggle(anim) for a drawer: if it is running forwards, reverse it; if it is finished or idle, play it forwards; if it is running backwards, play it forwards again.
Write scrubTo(anim, fraction) that moves an animation to a fraction (0 to 1) of its duration, whatever its delay.
Done when you can
You can start, pause, reverse, finish and cancel an animation
You can seek with currentTime and explain why to pause first
You can name the edge cases: reverse at the start, rate 0, seek past the end
You know the difference between playbackRate and updatePlaybackRate()