Animation
a change in a page's look played out over time
What it is
On the web, an animation is most often a CSS animation: a named run of
keyframes, written once in an @keyframes rule and attached to an element
through the animation properties. The W3C’s CSS Animations Level 1
specification describes the mechanism as a way “to animate the values of CSS
properties over time, using keyframes”. Animated images, video and canvas
drawing move too; this article follows the declarative kind.
Each keyframe states property values at a point in the run: from or 0% at
the start, to or 100% at the end, and any percentages in between. The
browser fills in the values between them. The other animation properties set how
long one run lasts, how many times it repeats, whether alternate runs play in
reverse and whether it is running or paused. A repeat count of infinite means,
in the specification’s words, that the animation “will repeat forever”.
animation-fill-mode decides what shows outside the run: with none the
animation has no effect then, backwards applies the first keyframe during any
delay, and forwards keeps the values it ended on.
A transition is the near relation: where a changed property value would normally show at once, CSS Transitions spreads the change over a set duration. CSS Animations draws the line between the two: transitions trigger implicitly when a property value changes, while animations “are explicitly executed when the animation properties are applied”. A link that fades its colour on hover is a transition; a loading spinner that turns whether or not anything changed is an animation.
A newer specification, Scroll-driven Animations, swaps the clock for the page itself. An animation attached to a scroll progress timeline advances as a scroll container scrolls, from 0% at the start of the scrollable range to 100% at its end. When those two positions coincide, because there is nothing to scroll, the specification calls the timeline inactive.
Why it matters
Movement pulls attention, which is both the reason to use it and the trouble with it.
For some readers it is physically unpleasant. The Understanding document for WCAG 2.2 success criterion 2.3.3 explains that elements moving as a page scrolls can trigger reactions in people with vestibular, or inner-ear, disorders: dizziness, nausea and headaches. It names parallax scrolling, where backgrounds move at a different rate from the foreground, as an animation that is often non-essential, and notes that the impact can be severe enough to need bed rest to recover.
Readers can say so, and a page can ask. Media Queries Level 5 defines
prefers-reduced-motion, a media feature with two values. reduce means the
reader has told the system they prefer an interface that “removes or replaces
the types of motion-based animation” that cause discomfort for people with
vestibular motion sensitivity or distraction for people with attention
deficits. no-preference means no preference is known, and it evaluates as
false when the feature is tested on its own.
Two success criteria cover it. 2.2.2 Pause, Stop, Hide, at level A, applies to moving, blinking or scrolling content that starts automatically, lasts more than five seconds and is shown alongside other content: the reader must be able to pause it, stop it or hide it, except where the movement is essential. It applies to everything on the page, and an animation that stops only while focus rests on it does not count as a pause. 2.3.3 Animation from Interactions, at level AAA, asks that motion animation triggered by an interaction can be disabled, unless it is essential. Its Understanding document lists the reduce-motion feature of the browser or operating system as one way to meet it.
Not every change is motion. WCAG defines motion animation as the “addition of steps between conditions to create the illusion of movement or to give a sense of a smooth transition”. An element that moves into position or changes its size as it appears counts as animated; one that appears instantly is not. A change of colour or opacity that leaves the element’s perceived size, shape and position alone falls outside the definition, which is why reducing motion does not mean freezing every fade.
What it looks like in practice
The plainest lesson starts with a rule that looked finished. The site honoured the reduce-motion preference with a single universal rule switching every transition off; it read as complete and passed its linter, yet under an emulated preference eleven selectors were still animating. The work that made the reduced-motion setting honest traced the cause to specificity: a universal selector loses to any rule that carries a class, which is every considered animation on the page. Everything that moves now lives in one late stylesheet part, and a linter fails a transitioned transform anywhere else. The line it draws is reduce motion, not colour, so a colour fade passes and a transition on movement does not. Motion is also expressed as a transition rather than a keyframe animation, because the first is trivially cancellable and the second is not. As the page puts it, a rule that loses on specificity fails silently.
Scroll-driven motion has a failure of its own. Among the 11 accessibility defects the browser reported as healthy was a scroll-driven animation, inactive on a page too short to scroll, that had permanently clipped the footer off three pages. The automated rules had passed it, because the defect lay in how the page rendered, not in its markup.
A linter can make the check impossible to skip. A 48-token design system, built on the platform’s own glass material, keeps motion as one of its six token groups, and its linter rejects an animation declared without honouring the reduce-motion preference. The page gives the reason: an animation is added in a moment of polish, the preference check is the part that gets skipped, and so the rule makes the animation impossible to write without it.
This site’s stylesheets show the same habits elsewhere. The two gears in the
masthead turn on 60-second and 96-second loops, and both are set to animation: none under reduce. Their scroll-linked parallax is gated twice, inside
prefers-reduced-motion: no-preference and inside an @supports test for
animation-timeline: scroll(); in a browser without the feature the gears stay
where they are, and the comment calls that fallback the design. In the footer,
the credit block that holds the copyright takes its hidden state from the
animation’s backwards fill rather than from a declared opacity: 0. The comment
gives the reason: an inactive timeline applies no effect at all, and the old
opacity: 0 had hidden the copyright on every short page.
Where it goes wrong
- The off switch loses the cascade. A blanket
transition: noneon the universal selector has almost no specificity, so any rule with a class keeps moving and nothing reports it. A block placed after the rules it has to beat, naming the moving selectors, makes the still state win. - The preference is added later, or never. An animation in the base style
moves for everyone until somebody remembers the cancel. Declaring it inside
@media (prefers-reduced-motion: no-preference)makes stillness the default. - A scroll timeline meets a page with nothing to scroll. The timeline is
inactive and the animation does nothing, so a starting state written into the
base style, such as an
opacity: 0waiting to be animated away, stays in force. A starting state supplied by the backwards fill goes with the animation. - A keyframe value the browser rejects drops out quietly. The specification says that a property specified but invalid in a keyframe is animated as if that keyframe did not exist. A misspelt value skips a step without an error.
- A decorative loop runs with no way to stop it. An
infiniteanimation that starts on load beside the text falls under 2.2.2 once it passes five seconds; a pause control, or an end within five seconds, answers it. - “Reduce” is read as “remove everything”. Cutting every state change can take information away with the movement. Showing the new state instantly, or with a fade, keeps the information and drops the motion.
Further reading
- CSS Animations Level 1
— the
@keyframesrule, the animation properties, fill modes and the events an animation fires. - CSS Transitions Level 1 — how a change of property value is spread over a duration instead of being applied at once.
- Scroll-driven Animations
— scroll progress timelines, the
scroll()notation and the cases in which a timeline is inactive. - Media Queries Level 5: prefers-reduced-motion
— the two values and what a reader who chooses
reduceis asking for. - Understanding SC 2.2.2: Pause, Stop, Hide — the five-second limit, what counts as starting automatically and which pause mechanisms qualify.
- Understanding SC 2.3.3: Animation from Interactions — vestibular reactions, parallax, and the reduce-motion preference as one way to meet the criterion.