Transitions & Keyframes
The cheap-and-fast way to move things.
Static interfaces feel cheap. The difference between a $5 site and a $50,000 product is usually a hundred milliseconds of motion in the right places. CSS gives you two tools for that: transitions (animate between two states) and keyframes (animate through a custom sequence). This lesson teaches both, and also when not to use them.
Transitions: animate between two states
A transition is the easiest kind of animation. You tell CSS "if this property changes, slide between the old and new values." Then you change the value (via :hover, a class swap, whatever) and the browser does the rest.
.button {
background: #4f46e5;
transform: scale(1);
transition: transform 200ms ease, background 200ms ease;
}
.button:hover {
background: #6366f1;
transform: scale(1.05);
}The shorthand is transition: <property> <duration> <easing> <delay>. List multiple properties separated by commas. Use allto transition everything (but be careful, this can animate things you didn't want, like text color when you change a theme).
Easing: where motion gets its personality
Easing is the curve that maps elapsed time to animation progress. A linear easing makes things look mechanical. A real-world easing makes them feel physical, like they have weight.
ease: starts fast, ends slow. The default. Fine for most.ease-in: starts slow, ends fast. Good for things leaving the screen.ease-out: starts fast, ends slow. Good for things entering the screen.ease-in-out: slow at both ends, fast in the middle. The most "natural" feel.linear: constant speed. Use for things that should feel mechanical (loading bars, progress).
cubic-bezier(): custom easing curves
When the named easings aren't enough, define your own with cubic-bezier(x1, y1, x2, y2). The four numbers are the two control points of a Bezier curve. The Chrome DevTools easing editor is your friend here, but a few classics:
cubic-bezier(0.4, 0, 0.2, 1): Material Design's standard curve. Smooth and elegant.cubic-bezier(0.34, 1.56, 0.64, 1): an overshoot, like a spring. Great for popping into existence.cubic-bezier(0.68, -0.55, 0.265, 1.55): a back-and-out (overshoots both ends).
linear() with stops: the new kid
Shipped in 2023: linear() takes a list of stops and creates a piecewise-linear curve. Sounds boring, but it lets you approximate spring animations (which are not Bezier curves) inside pure CSS:
.bounce {
/* A spring-like curve approximated as 8 stops */
transition: transform 600ms linear(
0, 0.0001, 0.001, 0.005, 0.02, 0.05, 0.1,
0.3 30%, 0.6, 0.9, 1.1 60%, 1.0, 0.95, 1.0
);
}Tools like linear-easing-generator convert spring physics to linear() output, so you can copy-paste the right curve. No Bezier needed.
.box { transition: transform 600ms ease-out; }
.box.active { transform: translateX(200px); }@keyframes: animate through a sequence
When two states aren't enough, define an animation explicitly:
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.7; }
100% { transform: scale(1); opacity: 1; }
}
.notification {
animation: pulse 1.5s ease-in-out infinite;
}The shorthand on the element is animation: <name> <duration> <easing> <delay> <iteration-count> <direction> <fill-mode>. The two you'll mess up most:
- iteration-count: a number, or
infinite. - fill-mode:
forwardskeeps the final state after the animation ends.backwardsapplies the starting state during any delay. Forgettingforwardsis the cause of "why does my element snap back at the end?"
What to animate: transform and opacity, nothing else
Browsers can animate transform and opacity on the GPU, which means smooth 60+ fps even on phones. Almost every other property (width, height, top, left, margin, padding) triggers layout or paint on every frame, which is slow.
transition: left 200ms and animating from left: 0 to left: 200px works but is choppy. Use transition: transform 200ms with translateX(200px) instead. Same visual result, ten times the frame rate.prefers-reduced-motion: respect the user
Some users get nauseous from motion. Some have vestibular disorders. Their OS lets them say "please cut down on animations," and your CSS can honor that:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}That nuclear option disables effectively all motion. A gentler take is to keep the animations but make them shorter and simpler. The principle is: don't move things across the screen, don't spin or wobble, don't parallax. Fades and small color shifts are usually fine.
Performance shortlist
- Animate
transformandopacity. Avoid anything else. - Use
will-change: transformon elements you're about to animate, but remove it after. Permanentwill-changewastes memory. - Keep durations under 400ms for most UI transitions. Longer feels sluggish.
- Use
prefers-reduced-motion. Always.
Quiz
Which two CSS properties can be animated cheaply on the GPU?
Recap
- Transitions animate between two states triggered by class or pseudo-class changes.
- @keyframes defines multi-step animations. Apply via
animation:on an element. - Easing sets the personality.
ease-outfor things entering,ease-infor leaving, customcubic-bezier()orlinear()when needed. - Animate
transformandopacityfor 60fps. Anything else costs frames. - Use
animation-fill-mode: forwardsto keep the final state. - Honor
prefers-reduced-motion. Always.