CSS animations and transitions bring interfaces to life. While both create motion, they serve different purposes: transitions interpolate between two states (start → end), while animations support multi-step sequences with自主 control over timing, iteration, and direction.
Performance is critical for smooth animations. The browser rendering pipeline has three key stages: Layout, Paint, and Composite. Animating transform and opacity only triggers compositing — the cheapest stage — while animating width, height, or top/left triggers expensive Layout recalculations on every frame.
CSS transitions interpolate between property values when a state change occurs (e.g., :hover, class toggle, JS-driven changes). A transition requires four sub-properties defining what, how long, what timing, and when to start.
/* Reverse transition is often faster (good UX) */
18
.card {
19
transition:
20
transform 0.25s ease,
21
box-shadow 0.25s ease;
22
}
23
24
.card:hover {
25
transition:
26
transform 0.2s cubic-bezier(0.34, 1.56, 0.64,1),
27
box-shadow 0.2s ease;
28
}
preview
Timing Functions
Timing functions (easing curves) define the rate of change during an animation or transition. The choice of easing curve dramatically affects how motion feels — whether mechanical and robotic or natural and polished.
The transform property modifies the coordinate space of an element, enabling translation, rotation, scaling, and skewing. Multiple transform functions can be chained in a single declaration.
2D Transform Functions
transform-2d.css
CSS
1
/* Translate — move the element */
2
.translate { transform: translateX(20px); }
3
.translate { transform: translateY(-10px); }
4
.translate { transform: translate(20px, -10px); }
5
6
/* Rotate — rotation around a point */
7
.rotate { transform: rotate(45deg); }
8
.rotate { transform: rotate(-0.25turn); } /* turns, grad, rad */
3D transforms extend the 2D functions with the Z-axis. Requires perspective on the parent to create depth.
transform-3d.css
CSS
1
/* Perspective — defines the depth of the 3D scene */
2
.stage {
3
perspective: 800px;
4
perspective-origin: center;
5
}
6
7
/* 3D transforms */
8
.rotate-x { transform: rotateX(45deg); }
9
.rotate-y { transform: rotateY(45deg); }
10
.rotate-z { transform: rotateZ(45deg); }
11
.translate-z { transform: translateZ(50px); }
12
.scale-z { transform: scaleZ(2) rotateX(45deg); }
13
14
/* 3D matrix */
15
.matrix3d {
16
transform: matrix3d(
17
1,0,0,0,
18
0,1,0,0,
19
0,0,1,0,
20
0,0,0,1
21
);
22
}
23
24
/* transform-style — preserves 3D space for children */
25
.stage {
26
perspective: 1000px;
27
transform-style: preserve-3d;
28
}
29
30
/* backface-visibility — hide the reverse side */
31
.card-back {
32
backface-visibility: hidden;
33
}
34
35
/* Card flip example */
36
.card-container {
37
perspective: 1000px;
38
}
39
40
.card {
41
transform-style: preserve-3d;
42
transition: transform 0.6s;
43
}
44
45
.card:hover {
46
transform: rotateY(180deg);
47
}
48
49
.card-front, .card-back {
50
backface-visibility: hidden;
51
}
52
53
.card-back {
54
transform: rotateY(180deg);
55
}
preview
Animation Properties Reference
CSS animations have approximately 20 sub-properties across the animation-* and related properties. Understanding each gives you precise control over motion behavior.
Property
Type
Description
animation
Shorthand
All animation-* properties in one declaration
animation-composition
Sub-property
replace | add | accumulate — how animations combine
animation-delay
Sub-property
Time before animation starts (negative for mid-start)
animation-direction
Sub-property
normal | reverse | alternate | alternate-reverse
animation-duration
Sub-property
Length of one complete cycle
animation-fill-mode
Sub-property
none | forwards | backwards | both
animation-iteration-count
Sub-property
Number or infinite
animation-name
Sub-property
Name of the @keyframes at-rule
animation-play-state
Sub-property
running | paused
animation-timeline
Sub-property
auto | scroll() | view() — scroll-driven animations
animation-timing-function
Sub-property
Easing curve for the animation
animation-range
Sub-property
normal | <length-percentage> — scroll range
animation-range-start
Sub-property
Start of animation scroll range
animation-range-end
Sub-property
End of animation scroll range
animation-shorthand.css
CSS
1
/* Complete animation shorthand */
2
.element {
3
animation: slideIn 0.6s ease-out 0.2s1 normal both running;
4
}
5
6
/* Order: name | duration | timing | delay | count | direction | fill | play-state */
7
/* Alternatively, use longhands for clarity */
8
.element {
9
animation-name: slideIn;
10
animation-duration: 0.6s;
11
animation-timing-function: ease-out;
12
animation-delay: 0.2s;
13
animation-iteration-count: 1;
14
animation-direction: normal;
15
animation-fill-mode: both;
16
animation-play-state: running;
17
animation-composition: replace;
18
}
19
20
/* Multiple animations */
21
.element {
22
animation:
23
fadeIn 0.5s ease both,
24
slideUp 0.5s ease 0.1s both;
25
}
Performance
Smooth 60fps animations require understanding the browser rendering pipeline. The key insight: only transform and opacity are compositor-only properties that skip layout and paint stages entirely.
Property
Triggers Layout
Triggers Paint
Triggers Composite
transform
✗
✗
✓
opacity
✗
✗
✓
width / height
✓
✓
✓
top / left
✓
✓
✓
color / background
✗
✓
✓
filter
✗
✓
✓
animation-performance.css
CSS
1
/* GOOD — compositor-only, 60fps */
2
.good {
3
transform: translateX(100px);
4
opacity: 0.5;
5
}
6
7
/* BAD — triggers layout, janky */
8
.bad {
9
width: 200px;
10
height: 200px;
11
top: 50px;
12
left: 100px;
13
}
14
15
/* Use transform instead of top/left for positioning animations */
/* Use sparingly — creates a layer, consumes memory */
26
}
27
28
/* Force GPU acceleration */
29
.gpu {
30
transform: translateZ(0);
31
/* Only use if profiling shows benefit */
32
}
⚠
warning
Overusing will-change creates too many compositor layers and consumes GPU memory. Only apply it to elements you know will animate, and consider removing it after the animation completes via JavaScript.
Scroll Animations
CSS scroll-driven animations allow you to tie animation progress to scroll position using animation-timeline. Combined with the Intersection Observer API for JS-driven triggers, these create engaging scroll-based experiences.
Scroll-Driven Animations (CSS)
scroll-animations.css
CSS
1
/* Scroll-timeline — ties animation to scroll progress */
2
@keyframes fadeInScroll {
3
from { opacity: 0; transform: translateY(30px); }
4
to { opacity: 1; transform: translateY(0); }
5
}
6
7
.scroll-reveal {
8
animation: fadeInScroll 1s ease both;
9
animation-timeline: view();
10
animation-range: entry 0% entry 100%;
11
}
12
13
/* Named scroll-timeline */
14
.container {
15
scroll-timeline: --container-scroll block;
16
}
17
18
.scroll-item {
19
animation: progress 1s linear;
20
animation-timeline: --container-scroll;
21
}
22
23
@keyframes progress {
24
from { width: 0; }
25
to { width: 100%; }
26
}
Intersection Observer (JS + CSS)
intersection-observer.js
JavaScript
1
// JavaScript trigger for scroll-based animations
2
const observer = new IntersectionObserver((entries) => {
Many users experience motion sickness, dizziness, or cognitive overload from animations. The prefers-reduced-motion media query allows you to respect user system preferences for reduced motion.
accessible-animations.css
CSS
1
/* Respect user motion preferences */
2
@media (prefers-reduced-motion: reduce) {
3
*, *::before, *::after {
4
animation-duration: 0.01ms !important;
5
animation-iteration-count: 1 !important;
6
transition-duration: 0.01ms !important;
7
scroll-behavior: auto !important;
8
}
9
}
10
11
/* Selective disabling — better than blanket */
12
@media (prefers-reduced-motion: reduce) {
13
.bounce, .pulse, .spin{
14
animation: none;
15
}
16
17
.hover-lift {
18
transform: none !important;
19
}
20
}
21
22
/* Provide reduced-motion alternatives */
23
.card {
24
transition: transform 0.3s ease;
25
}
26
27
.card:hover {
28
transform: scale(1.05);
29
}
30
31
@media (prefers-reduced-motion: reduce) {
32
.card:hover {
33
transform: none;
34
outline: 2px solid currentColor;
35
}
36
}
37
38
/* Animate from a state that doesn't require motion */
39
@media (prefers-reduced-motion: no-preference) {
40
.fade-in {
41
animation: fadeIn 0.5s ease both;
42
}
43
}
✓
best practice
Always wrap non-essential animations in @media (prefers-reduced-motion: no-preference). This ensures that users with vestibular disorders or motion sensitivity can use your site comfortably. For essential motion (loading spinners, progress bars), provide a static alternative.
Best Practices
◆Only animate transform and opacity for 60fps performance — avoid layout-triggering properties
◆Use cubic-bezier spring curves (0.34, 1.56, 0.64, 1) for natural-feeling interactions
◆Keep transition durations short: 150-300ms for micro-interactions, 300-500ms for page transitions
◆Use ease-out for elements entering the screen, ease-in for elements leaving
◆Respect prefers-reduced-motion — provide non-animated fallbacks
◆Staggered animations should use 50-100ms delays for subtle sequencing
◆Use animation-delay with negative values to start animations mid-cycle
◆Avoid animating width/height — use transform: scaleX()/scaleY() instead
◆Use will-change sparingly and only on elements about to animate
◆Test animations on low-powered devices (e.g., mid-range Android phones)
◆Use framer-motion or GSAP for complex choreographies that CSS alone cannot handle
◆Design animations that work at any duration — they should make sense at 0.01ms for reduced motion
🔥
pro tip
Use the browser DevTools Performance panel to record animations and identify jank. Look for red bars in the "Frames" section — these indicate dropped frames. The Animations panel in Chrome DevTools lets you slow down, replay, and inspect individual animations.
Interactive Animation Demo
A visually impressive demo combining multiple animation techniques: transform, opacity, staggered delays, and hover interactions.