Animations & Transitions
Checking access...
Motion enhances user experience when done right — it guides attention, provides feedback, and adds polish. CSS provides two motion systems: transitions (simple state changes) and animations (complex sequences).
CSS Transitions
Transitions smoothly change property values over time when an element changes state:
.button { background: blue; color: white; padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer;
/* Define the transition */ transition: background-color 0.3s ease;}
.button:hover { background: darkblue;}Transition Properties
.element { /* Shorthand: property duration timing-function delay */ transition: opacity 0.3s ease 0s;
/* Multiple properties */ transition: opacity 0.3s ease, transform 0.2s ease;
/* All animatable properties */ transition: all 0.3s ease;
/* Longhand */ transition-property: opacity, transform; transition-duration: 0.3s, 0.2s; transition-timing-function: ease, ease; transition-delay: 0s, 0s;}Timing Functions
transition-timing-function: ease; /* Default: slow start, fast middle, slow end */transition-timing-function: linear; /* Constant speed */transition-timing-function: ease-in; /* Slow start, fast end */transition-timing-function: ease-out; /* Fast start, slow end */transition-timing-function: ease-in-out; /* Slow start and end */
/* Custom cubic bezier */transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); /* Bounce effect */Practical Transition Examples
/* Card hover: subtle lift and shadow */.card { transition: transform 0.2s ease, box-shadow 0.2s ease;}.card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgba(0,0,0,0.12);}
/* Button hover: scale effect */.button { transition: transform 0.15s ease;}.button:hover { transform: scale(1.05);}.button:active { transform: scale(0.98);}
/* Fade in on load */.fade-in { opacity: 0; animation: fadeIn 0.5s ease forwards;}CSS Animations
Animations give more control than transitions — multiple steps, looping, and bidirectional control.
Keyframes
Define the animation sequence:
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); }}
/* Percentage-based (more control) */@keyframes slideIn { 0% { opacity: 0; transform: translateX(-100%); } 60% { opacity: 1; transform: translateX(10px); /* Overshoot */ } 100% { transform: translateX(0); }}Animation Properties
.element { /* Required: animation name and duration */ animation-name: fadeIn; animation-duration: 0.5s;
/* Optional */ animation-timing-function: ease; animation-delay: 0s; animation-iteration-count: 1; animation-direction: normal; animation-fill-mode: none; animation-play-state: running;
/* Shorthand */ animation: fadeIn 0.5s ease 0s 1 normal forwards;}| Property | Values | Effect |
|---|---|---|
name | Keyframe name | Which animation to run |
duration | 0.5s, 2000ms | How long one cycle takes |
timing-function | ease, linear, ease-in-out | Speed curve |
delay | 0.5s, 1s | Wait before starting |
iteration-count | 1, 3, infinite | How many times to play |
direction | normal, reverse, alternate, alternate-reverse | Play direction |
fill-mode | none, forwards, backwards, both | Style before/after animation |
play-state | running, paused | Control playback |
Fill Modes
.element { /* forwards: retains the final keyframe style */ animation: fadeIn 0.5s ease forwards;
/* backwards: applies initial keyframe style during delay */ animation: slideIn 0.5s ease 0.3s backwards;}The transform Property
Transform modifies an element’s appearance without affecting layout:
.element { /* 2D transforms */ transform: translateX(50px); /* Move horizontally */ transform: translateY(-20px); /* Move vertically */ transform: translate(50px, -20px); /* Move both */
transform: rotate(45deg); /* Rotate clockwise */ transform: scale(1.5); /* Scale up 150% */ transform: scaleX(2); /* Stretch horizontally */ transform: skew(10deg); /* Skew */
/* 3D transforms */ transform: perspective(500px) rotateY(45deg); /* 3D card flip */
/* Multiple transforms (order matters!) */ transform: translateX(50px) rotate(45deg) scale(1.2);
/* Transform origin (default: center) */ transform-origin: top left; transform-origin: 0 0;}Performance Considerations
Animate Only These Properties
For smooth 60fps animations, only animate:
/* GPU-accelerated — always smooth */.element { transform: ...; /* translate, scale, rotate */ opacity: ...; /* 0 to 1 */}These two properties are GPU-accelerated — the browser composites them without re-layout.
Avoid Animating These
/* ❌ These trigger layout recalculation — expensive */.element { width: ...; height: ...; margin: ...; padding: ...; top: ...; left: ...;}Every time a layout-triggering property changes, the browser recalculates all element positions, which can cause jank.
will-change
Hint the browser about upcoming changes:
.element { will-change: transform; /* Tell browser: this will animate */}Use sparingly — too many will-change declarations consumes memory.
prefers-reduced-motion
Respect user accessibility settings:
/* Remove all animations for users who prefer reduced motion */@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }}Complete Animation Examples
Loading Spinner
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); }}
.spinner { width: 40px; height: 40px; border: 4px solid #eee; border-top-color: #0066cc; border-radius: 50%; animation: spin 0.8s linear infinite;}Skeleton Loading
@keyframes shimmer { 0% { background-position: -200px 0; } 100% { background-position: calc(200px + 100%) 0; }}
.skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200px 100%; animation: shimmer 1.5s infinite; height: 16px; margin-bottom: 8px; border-radius: 4px;}Page Reveal Animation
@keyframes reveal { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); }}
.reveal { opacity: 0; /* Start hidden */ animation: reveal 0.6s ease forwards;}
.reveal:nth-child(2) { animation-delay: 0.1s; }.reveal:nth-child(3) { animation-delay: 0.2s; }Try It Yourself
Create a page with:
- A button that smoothly changes color and scales on hover (transition)
- A card that lifts with shadow on hover (transition, transform)
- A loading spinner (keyframe animation, infinite)
- Elements that fade in one after another (animation with delays)
- A notification that slides in, pauses, then slides out (keyframes with percentage stops)
- Test with
prefers-reduced-motion: reducein Chrome DevTools
Key Takeaways
- Use
transitionfor simple state changes (hover, active, focus) - Use
@keyframes+animationfor complex multi-step sequences - Only animate
transformandopacityfor smooth 60fps performance - Avoid animating layout properties (width, height, margin, padding, top, left)
animation-fill-mode: forwardsretains the final state after animation ends- Always include
prefers-reduced-motion: reducefor accessibility transform: translate()is for positioning, nottop/left- Use
animation-delayto create staggered reveal sequences