The web has always rested on three pillars: HTML controls structure, CSS controls presentation, JavaScript controls behaviour.
At least, that’s the theory. It’s valid up to a point… but like any taxonomy, the boundaries between the regions are a little fuzzy, and they even move around from time to time.
Animation was historically considered behaviour, but as the web has matured, we’ve realised that much of the time animation is actually about presentation. Movement and motion can be used to draw your user’s attention to an element, such as an invalid form field; to show and hide dynamic navigation elements, or to create a sense of flow as people move between different parts of a page or application. None of these things change the underlying behaviour of the system—they’re all about how information is presented and how the experience feels.
Consequently, animation features like transitions, keyframes and easing functions have gradually moved out of JavaScript and become part of CSS.
Animation and Accessibility
TODO: write the stuff about animation and accessibility!
Animation using transition
Here’s a page with three <div> elements on it. Each of them does something different when you hover the mouse over it:
* { box-sizing: border-box; }
body {
text-align: center;
height: 140px;
}
div {
margin: 10px;
width: 200px;
height: 70px;
padding-top: 20px;
font-size: 1.5rem;
background-color: crimson;
color: #fff;
font-weight: bold;
display: inline-block;
vertical-align: top;
&:nth-of-type(1):hover {
background-color: royalblue;
}
&:nth-of-type(2):hover {
height: 130px;
}
&:nth-of-type(3):hover {
font-family: Arial, Helvetica, sans-serif;
}
}
<div>hover over me</div>
<div>hover over me</div>
<div>hover over me</div>Usually, when we change a property of an element - change its background colour, make it wider, change its position - the change happens instantaneously. Using transitions, we can create smooth animations from the initial state to the final state by specifying:
transition-property: what are we animating? (Useallto animate everything that’s animatable)transition-duration: How long the transition should take - in seconds (1s) or milliseconds (100ms)transition-timing-function: for smooth animations, how should we calculate intermediate stages?transition-delay: how long to wait before running the transition (default:0)
You can specify each one individually:
transition-property: color, margin-right, height;
transition-duration: 1s, 2s, 3s;
transition-timing-function: linear, ease-in, ease-out;
transition-delay: 3s, 2s, 1s;
But this is definitely a scenario where you’re better off using the equivalent shorthand property:
transition:
color 1s linear 3s,
margin-right 2s ease-in 2s,
height 3s ease-out 1s;
Here’s our set of <div> elements with a very basic transition applied:
* {
box-sizing: border-box;
}
body {
text-align: center;
height: 140px;
}
div {
margin: 10px;
width: 200px;
height: 70px;
padding-top: 20px;
font-size: 1.5rem;
background-color: crimson;
color: #fff;
font-weight: bold;
display: inline-block;
vertical-align: top;
&:nth-of-type(1) {
transition: background-color 1s;
&:hover {
background-color: royalblue;
}
}
&:nth-of-type(2) {
transition: height 1s;
&:hover {
height: 130px;
}
}
&:nth-of-type(3) {
transition: font-family 1s;
transition-behavior: allow-discrete;
&:hover {
font-family: Arial, Helvetica, sans-serif;
}
}
}
<div>hover over me</div>
<div>hover over me</div>
<div>hover over me</div>What Can We Animate?
The short answer is… just about anything. The vast majority of properties in CSS can be animated; a full list of them all would take way more time than we have here, and by the end of it we’d all be extremely bored.
Broadly speaking, there are two kinds of animatable properties in CSS - continuous, and discrete. Continuous animation (also known as “computed value”) means the browser can calculate the full range of intermediate values, and so create a smooth animated transition from the initial to the final state.
For our background-color example, it calculates the intermediate red, green, and blue values for every animation frame; for the height example, it calculates the intermediate heights.
For something like font-family, there’s no mathematical way to specify what’s halfway between Times New Roman and Arial, so the animation is discrete. By default, this means it’ll flip immediately, but in this case we’ve used the transition-behavior: allow-discrete property, which will flip it at the halfway point of the transition duration; in this case, we’ve specified the duration as 1s so the font family changes after half a second.
Transition Delays
Every transition can take an extra delay parameter, specified in seconds or milliseconds.
Negative delays are allowed, which might seem impossible — how can you have a button that starts to change colour two seconds before the user has decided to click on it? — but what they actually do is to start the transition partway through, as if it had already been running for the specified period. If the negative delay is equal to, or longer than, the transition duration, you won’t see any animation - it’s already done and jumps straight to the final state.
* {
box-sizing: border-box;
}
body {
font-family: Consolas, 'Courier New', Courier, monospace;
color: #fff;
display: flex;
a {
display: block;
background-color: crimson;
padding: 5px;
margin: 5px;
display: flex;
text-align: center;
align-items: center;
justify-content: center;
}
}
section {
div {
width: 35vw;
background-color: forestgreen;
padding: 5px;
margin: 5px;
transition: all 1s;
&:hover {
width: 90vw;
}
}
}
a:hover+section div {
width: 90vw;
}
<a>
PLAY<br>
ALL
</a>
<section>
<div style="transition-delay: -1s;">transition-delay: -1s</div>
<div style="transition-delay: -0.5s;">transition-delay: -0.5s</div>
<div style="transition-delay: 0s;">transition-delay: 0s</div>
<div style="transition-delay: 0.5s;">transition-delay: 0.5s</div>
<div style="transition-delay: 1s;">transition-delay: 1s</div>
</section>Timing Functions
Timing functions create smooth, natural animations by simulating real-world physics effects like inertia. First, an interactive example:
* {
box-sizing: border-box;
}
body {
font-family: Consolas, 'Courier New', Courier, monospace;
color: #fff;
display: flex;
a {
display: block;
background-color: crimson;
padding: 5px;
margin: 5px;
display: flex;
text-align: center;
align-items: center;
justify-content: center;
}
}
section {
div {
width: 35vw;
background-color: forestgreen;
padding: 5px;
margin: 5px;
transition: width 2s;
&:hover {
width: 75vw;
}
}
}
a:hover+section div {
width: 75vw;
}
<a>
PLAY<br>
ALL
</a>
<section>
<div style="transition-timing-function:ease;">ease</div>
<div style="transition-timing-function:ease-in;">ease-in</div>
<div style="transition-timing-function:ease-out;">ease-out</div>
<div style="transition-timing-function:ease-in;">ease-in-out</div>
<div style="transition-timing-function:linear;">linear</div>
<div style="transition-timing-function:step-start;">step-start</div>
<div style="transition-timing-function:step-end;">step-end</div>
<div style="transition-timing-function:steps(5);">steps(5)</div>
<div style="transition-timing-function:steps(2, jump-start);">steps(2, jump-start)</div>
<div style="transition-timing-function:steps(2, jump-end);">steps(2, jump-end)</div>
<div style="transition-timing-function:steps(2, jump-none);">steps(2, jump-none)</div>
<div style="transition-timing-function:steps(2, jump-both);">steps(2, jump-both)</div>
<div
style="transition-timing-function:cubic-bezier(0.25, -0.5, 0.75, 1.5);">
cubic-bezier(0.25, -0.5, 0.75, 1.5)
</div>
</section>Timing functions are based on a mathematical function called a cubic Bezier curve. Sounds complicated. It’s not, really — or rather, it uses some complicated mathematics behind the scenes, but you don’t need to grok the maths to build your own curves, because there’s a Bezier curve viewer and editor built in to browser dev tools.
I actually prefer the curve editor in Firefox, because it’s easier to create curves which overshoot the animation range — useful to create a sort of bouncy kinetic effect which I rather like. Open the Inspector, find an element with a transition timing function, and click the tiny icon next to it:

Once open, you can use the editor to select from various preset timing functions, or create your own timing curves by dragging the Bezier node handles; it’ll update the CSS in the Styles inspector and you can copy the results back into your own code.

The equivalent in Chrome is accessed in a very similar way, and provides mostly the same capability — the only limitation is that the size of the curve editor is limited because it assumes we’re not going to overshoot.

You can even build your own timing function visualiser in a few lines of code, by animating the position (top and left) of an element and using the linear timing function for the left transition:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Timing Function Visualiser</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
padding: 10px;
gap: 10px;
}
div {
flex: 0 0 22vw;
aspect-ratio: 1;
border: 1px solid #000;
font-family: Consolas, 'Courier New', Courier, monospace;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.6em;
position: relative;
span {
display: block;
position: absolute;
left: 0px;
bottom: 0px;
width: 20px;
height: 20px;
background-color: red;
border-radius: 100%;
transition: left 1s, bottom 1s;
}
&:hover span {
left: calc(100% - 20px);
bottom: calc(100% - 20px);
}
}
</style>
</head>
<body>
<div><span style="transition-timing-function: linear, linear;"></span>
linear</div>
<div><span style="transition-timing-function: linear, ease;"></span> ease
</div>
<div><span style="transition-timing-function: linear, ease-in;"></span>
ease-in</div>
<div><span style="transition-timing-function: linear, ease-out;"></span>
ease-out</div>
<div><span style="transition-timing-function: linear, ease-in-out;"></span>
ease-in-out</div>
<div><span
style="transition-timing-function: linear, cubic-bezier(0.2,-0.5,0.8,1.5);"></span>
cubic-bezier(0.2,-1,0.8,2)</div>
<div><span style="transition-timing-function: linear, steps(3);"></span>
steps(3)</div>
<div><span
style="transition-timing-function: linear, steps(3, jump-start);"></span>
steps(3, jump-start)</div>
</body>
</html>As we saw a moment ago, the transition-property property accepts a keyword all, meaning “animate every property that’s animatable”. You can use to create some fairly dramatic effects:
html,
body {
margin: 0;
padding: 0;
}
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
span {
position: absolute;
left: 85vw;
top: 40vh;
width: 10px;
height: 10px;
border: 5px solid white;
border-radius: 100%;
pointer-events: none;
display: block;
&::after {
color: #fff;
position: absolute;
top: 15px;
left: -15px;
content: 'put your mouse here!';
text-align: center;
}
}
div {
background-color: purple;
font-family: Arial, Helvetica, sans-serif;
font-size: 4vw;
padding: 1rem;
color: gold;
width: 80vw;
height: 20vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
border-width: 2rem 1rem;
border-style: solid;
border-color: gold;
&:hover {
transition: all 1s;
border-radius: 5rem;
border-color: royalblue;
border-width: 1rem 2rem;
background-color: crimson;
color: white;
font-size: 1vw;
width: 20vw;
height: 80vh;
}
}
<div>hover!</div>
<span></span>
If you look closely, you’ll also see that this example only animates in one direction. The transition property is only applied to the :hover state, so the animations will only play when the element transitions from normal to hover; when you mouse-out of the element, it snaps back immediately.
Oh, and watch out for horrible flicker loops… see what happens if you hover over the “put your mouse here” <span> in this example? Yeah. Not great user experience.
Now, in one sense, that’s everything you need to know about CSS transitions: property, duration, timing function, delay, and transition-behaviour: allow-discrete to include discrete properties like font family. That’s the complete transition feature set. In another sense, we’ve barely scratched the surface of what you can do with CSS transitions.
Advanced Animation
Sometimes you need something more complex than a smooth interpolation between the initial and the final state. That’s where the CSS animation module gets involved.
An animation in CSS has two components: a style describing the timing, duration and other parameters, and a named set of keyframes that dictate the initial state, final state, and any intermediate states.
Defining Keyframes
Here’s a simple keyframe animation that’ll change an element’s background colour from crimson to royal blue. This is the simplest possible example that actually works:
@keyframes really-cool-animation-demo {
from {
background-color: crimson;
}
to {
background-color: royalblue;
}
}
div {
animation: really-cool-animation-demo 1s;
}Things to notice here:
- The animation has a name - I’ve used
really-cool-animation-demo- You can use anything you like, as long as it’s a valid
<custom-ident>- see below.
- You can use anything you like, as long as it’s a valid
- The animation runs once, when the page loads.
- The only way to run it again is to reload the page.
- When the animation’s done, it vanishes - we animate our
<div>from crimson to royal blue, but it doesn’t stay blue.
Custom Identifiers
Animation names in CSS must be a valid <custom-ident>: one or more characters, which can contain letters A-Z a-z, numbers 0-9, hyphens -, underscores _
- Number
0-9
TODO: finish writing about animations
Course Content
- Animations and CSS transitions
- Triggering interactions: hover, click, scroll, JS events
- Parallax scrolling
-
Exercise: animated airline departure board grid
- Designing Safer Web Animation For Motion Sensitivity · An A List Apart Article
-
[An Introduction to the Reduced Motion Media Query CSS-Tricks](https://css-tricks.com/introduction-reduced-motion-media-query/) -
[Responsive Design for Motion WebKit](https://webkit.org/blog/7551/responsive-design-for-motion/) - MDN Understanding WCAG, Guideline 2.2 explanations
-
[Understanding Success Criterion 2.2.2 W3C Understanding WCAG 2.0](https://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-pause.html)
Notes
One really important thing to note about CSS transforms is that on the vast majority of modern computers, they’re actually processed by the graphics processing unit (GPU) - you know that NVidia RTX5070 you bought to play Cyberpunk 2077? Yeah. By using CSS transforms, we can offload the visual processing to the GPU 0 a