Course outline
Reduced Motion and Pausing
In one line
Motion can make people ill and distract those who read. Honour the reduced-motion setting, give a pause button to anything that moves for more than five seconds, and stop drawing when nothing changes.
Think of it like this
A shop with a spinning neon sign. Most customers like it. One finds it hard to look at and asks the owner to switch it off. A good owner has a switch by the door, and does not make the customer ask twice.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- vestibular disorder
- A problem of the inner ear and balance system. Screen motion can cause dizziness and nausea.
- prefers-reduced-motion
- The media feature that says the visitor asked the system to reduce movement.
- WCAG 2.2.2
- A rule (Level A) that automatic motion lasting over 5 seconds must have a way to pause, stop or hide it.
- dirty flag
- A true/false value that says "something changed, draw again".
Step by step
01One rule
mayMove(state) returns false if paused, false if reduced motion and the visitor has not pressed Play, and true otherwise. The log prints the table of all combinations.
02One button
The button's text says what it will do: "Pause animation", "Play animation" (reduced motion, not started) or "Resume animation". Its label is always the action, so a screen reader announces it correctly.
03Listen for changes
query.addEventListener("change", ...) updates the state and the label when the system setting changes while the page is open.
04Skip idle frames
The second example is a loop with a dirty flag, fed with 120 fixed steps for each case and counting its real render() calls.
Try it yourself
- 1
Press Pause
Click the button: the stand stops and the label reads "Resume animation". Click again and it turns on.
- 2
Pretend to be a reduced-motion visitor
In Chrome, open the developer tools, then the Rendering panel (from the three-dot menu, More tools) and set "Emulate CSS media feature prefers-reduced-motion" to "reduce". The
changeevent fires, so the stand stops and the label reads "Play animation", with no reload. Press it: the label becomes "Pause animation" and the stand turns, because the visitor asked. Set the emulation back to "no emulation" afterwards. (The rule table above gives the same answers for each combination.) - 3
Count the draws
In the second example delete the line
dirty = true; // draw the final still frame once. The "paused for 2 s" row now printsdraws: 0. Then also delete the camera move in the third case: that row printsdraws: 0too. A paused page that draws nothing costs the GPU nothing.
Code & diagrams
Press the button, or Tab to it and press Enter. The table is the rule for every combination. In the browser's developer tools you can emulate `prefers-reduced-motion: reduce` to see the other half.
Console
Run the scene to see its console.Expected console output
reduced paused started by user moves?
false false false true
false true false false
true false false false
true false true true
true true true falseThe camera is moved by hand at frame 30 of the third case. The count is the number of real `render()` calls.
Console
Run the scene to see its console.Expected console output
turning frames: 120 draws: 120
paused for 2 s frames: 120 draws: 1
paused, camera moved at frame 30 frames: 120 draws: 1
playing again frames: 120 draws: 120Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget the keyboard
In the first example, change the <button> in the html to <div id="pause">Pause animation</div>.
Myth vs fact
Myth
Reduced motion means no animation at all.
Fact
It means no large, automatic movement. Motion the visitor causes, and short, small transitions, are usually fine.
Myth
A pause button is only needed for video.
Fact
WCAG 2.2.2 covers any content that moves by itself for more than 5 seconds, including a 3D turntable.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Do not decide only once at load time. The setting can change (the visitor opens system settings), and
matchMediafireschange. - ▸
Camera animations should become instant jumps under reduced motion, as
controls.jsalready does in Topic 7.5:goTocuts instead of flying. - ▸
Stopping the loop also stops
OrbitControlsdamping, so the bike can freeze mid-glide. Either keep rendering while the controls have momentum, or setenableDamping = falseon the idle loop.
Remember this
- 1
The problem this solves: large or fast movement (spinning, zooming, parallax) can cause dizziness and nausea for people with vestibular disorders, and can pull attention away for people with attention difficulties. Ravi, our screen-reader tester, wants to read the price and hear the panel without the bike turning in the background. A 3D page that never stops is also a battery drain.
- 2
**
prefers-reduced-motion** is a CSS media feature that mirrors the system setting ("Reduce motion" on macOS and iOS, "Animation effects" switched off on Windows 11 ("Show animations in Windows" on Windows 10), "Remove animations" on Android). In JavaScript:matchMedia("(prefers-reduced-motion: reduce)").matches. Listen for itschangeevent, because the setting can change while the page is open. - 3
What "reduce" means. Not "no movement ever". Motion that the visitor starts, such as dragging the bike, is fine. Motion that starts by itself, such as an idle turntable, should be off. Large camera flights should become cuts. Small, short fades are usually fine.
- 4
WCAG 2.2.2, Pause, Stop, Hide (Level A): moving, blinking or scrolling content that starts automatically, lasts more than five seconds and is presented in parallel with other content needs a mechanism to pause, stop or hide it (unless the movement is essential). A turntable beside a price and a buy button is in parallel with other content. A visible button that is reachable with the keyboard and says what it does satisfies it. A 3D turntable that never stops needs one.
- 5
The rule in one place.
mayMove = !paused && (!reducedMotion || startedByUser). The pause button always wins. With reduced motion, nothing moves until the visitor asks, and pressing Play counts as asking. Keeping the rule in one function means the keyboard, the button and the system setting cannot disagree. - 6
Stop drawing when nothing changes. A still scene redrawn 60 times a second still uses the GPU. A loop can check a
dirtyflag, render only when something moved or the camera did, and skip the rest. The second example draws 120 frames while turning, 1 while paused for two seconds, 1 when the camera moves once, and 120 again after Play. - 7
A background tab stops
requestAnimationFrameby itself. For a visible but idle page,renderer.setAnimationLoop(null)stops the loop and frees the GPU. The cost is that everything that needs a frame (damping on OrbitControls, an animating fade) must wake the loop again.
Explain it without notes
What does prefers-reduced-motion: reduce tell you, and what should a 3D page change because of it?
Which WCAG rule requires a pause button on a turntable, and what exactly must the button do?
Practice
Write mayMove for the rule in this topic.
Write the code that reacts when the visitor changes the system setting while the page is open.
Trade-offs
- ↔
Skipping idle frames saves battery but needs care: anything that animates (damping, a fade) must mark the scene dirty or it freezes.
Done when you can
Read the reduced-motion setting and react when it changes
Say what WCAG 2.2.2 asks of an automatic animation
Keep the "may anything move" rule in one function
Skip drawing frames when nothing changed