CSS animations enable rich motion without JavaScript. Here's how to create them.
Basic Animations#
1/* Define keyframes */
2@keyframes fadeIn {
3 from {
4 opacity: 0;
5 }
6 to {
7 opacity: 1;
8 }
9}
10
11/* Apply animation */
12.fade-in {
13 animation: fadeIn 0.3s ease-out;
14}
15
16/* With percentages */
17@keyframes bounce {
18 0%, 100% {
19 transform: translateY(0);
20 }
21 50% {
22 transform: translateY(-20px);
23 }
24}
25
26.bounce {
27 animation: bounce 0.6s ease-in-out;
28}
29
30/* Animation shorthand */
31.animated {
32 animation: fadeIn 0.5s ease-out 0.2s 1 normal forwards;
33 /* name | duration | timing | delay | iterations | direction | fill-mode */
34}Animation Properties#
1.detailed-animation {
2 animation-name: slideIn;
3 animation-duration: 0.5s;
4 animation-timing-function: ease-out;
5 animation-delay: 0.1s;
6 animation-iteration-count: 1; /* or infinite */
7 animation-direction: normal; /* reverse, alternate, alternate-reverse */
8 animation-fill-mode: forwards; /* none, backwards, both */
9 animation-play-state: running; /* or paused */
10}
11
12/* Timing functions */
13.linear { animation-timing-function: linear; }
14.ease { animation-timing-function: ease; }
15.ease-in { animation-timing-function: ease-in; }
16.ease-out { animation-timing-function: ease-out; }
17.ease-in-out { animation-timing-function: ease-in-out; }
18
19/* Custom cubic-bezier */
20.custom-ease {
21 animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
22}
23
24/* Steps for frame-by-frame */
25.steps {
26 animation-timing-function: steps(4, end);
27}Common Animation Patterns#
1/* Slide in from left */
2@keyframes slideInLeft {
3 from {
4 transform: translateX(-100%);
5 opacity: 0;
6 }
7 to {
8 transform: translateX(0);
9 opacity: 1;
10 }
11}
12
13/* Slide in from right */
14@keyframes slideInRight {
15 from {
16 transform: translateX(100%);
17 opacity: 0;
18 }
19 to {
20 transform: translateX(0);
21 opacity: 1;
22 }
23}
24
25/* Scale up */
26@keyframes scaleUp {
27 from {
28 transform: scale(0);
29 opacity: 0;
30 }
31 to {
32 transform: scale(1);
33 opacity: 1;
34 }
35}
36
37/* Rotate in */
38@keyframes rotateIn {
39 from {
40 transform: rotate(-180deg) scale(0);
41 opacity: 0;
42 }
43 to {
44 transform: rotate(0) scale(1);
45 opacity: 1;
46 }
47}
48
49/* Shake */
50@keyframes shake {
51 0%, 100% { transform: translateX(0); }
52 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
53 20%, 40%, 60%, 80% { transform: translateX(5px); }
54}
55
56/* Pulse */
57@keyframes pulse {
58 0%, 100% {
59 transform: scale(1);
60 }
61 50% {
62 transform: scale(1.05);
63 }
64}Loading Animations#
1/* Spinner */
2@keyframes spin {
3 to {
4 transform: rotate(360deg);
5 }
6}
7
8.spinner {
9 width: 40px;
10 height: 40px;
11 border: 3px solid #f3f3f3;
12 border-top: 3px solid #3498db;
13 border-radius: 50%;
14 animation: spin 1s linear infinite;
15}
16
17/* Dots loading */
18@keyframes dotPulse {
19 0%, 80%, 100% {
20 transform: scale(0);
21 opacity: 0;
22 }
23 40% {
24 transform: scale(1);
25 opacity: 1;
26 }
27}
28
29.loading-dots span {
30 display: inline-block;
31 width: 10px;
32 height: 10px;
33 background: #3498db;
34 border-radius: 50%;
35 animation: dotPulse 1.4s ease-in-out infinite;
36}
37
38.loading-dots span:nth-child(1) { animation-delay: -0.32s; }
39.loading-dots span:nth-child(2) { animation-delay: -0.16s; }
40.loading-dots span:nth-child(3) { animation-delay: 0s; }
41
42/* Bar loading */
43@keyframes barProgress {
44 0% {
45 width: 0%;
46 }
47 100% {
48 width: 100%;
49 }
50}
51
52.progress-bar {
53 height: 4px;
54 background: #3498db;
55 animation: barProgress 2s ease-out forwards;
56}
57
58/* Skeleton loading */
59@keyframes shimmer {
60 0% {
61 background-position: -200% 0;
62 }
63 100% {
64 background-position: 200% 0;
65 }
66}
67
68.skeleton {
69 background: linear-gradient(
70 90deg,
71 #f0f0f0 25%,
72 #e0e0e0 50%,
73 #f0f0f0 75%
74 );
75 background-size: 200% 100%;
76 animation: shimmer 1.5s infinite;
77}Multi-Step Animations#
1/* Complex entrance */
2@keyframes complexEntrance {
3 0% {
4 opacity: 0;
5 transform: translateY(50px) scale(0.9);
6 }
7 50% {
8 opacity: 0.5;
9 transform: translateY(-10px) scale(1.02);
10 }
11 100% {
12 opacity: 1;
13 transform: translateY(0) scale(1);
14 }
15}
16
17/* Typing effect */
18@keyframes typing {
19 from { width: 0; }
20 to { width: 100%; }
21}
22
23@keyframes blink {
24 50% { border-color: transparent; }
25}
26
27.typewriter {
28 overflow: hidden;
29 border-right: 2px solid;
30 white-space: nowrap;
31 animation:
32 typing 3s steps(30) forwards,
33 blink 0.7s step-end infinite;
34}
35
36/* Attention animation */
37@keyframes attention {
38 0% { transform: scale(1); }
39 14% { transform: scale(1.3); }
40 28% { transform: scale(1); }
41 42% { transform: scale(1.3); }
42 70% { transform: scale(1); }
43}
44
45.attention {
46 animation: attention 2s ease-in-out;
47}Staggered Animations#
1/* Staggered list items */
2.list-item {
3 opacity: 0;
4 animation: fadeInUp 0.5s ease forwards;
5}
6
7@keyframes fadeInUp {
8 from {
9 opacity: 0;
10 transform: translateY(20px);
11 }
12 to {
13 opacity: 1;
14 transform: translateY(0);
15 }
16}
17
18.list-item:nth-child(1) { animation-delay: 0.1s; }
19.list-item:nth-child(2) { animation-delay: 0.2s; }
20.list-item:nth-child(3) { animation-delay: 0.3s; }
21.list-item:nth-child(4) { animation-delay: 0.4s; }
22.list-item:nth-child(5) { animation-delay: 0.5s; }
23
24/* Using CSS custom properties */
25.stagger-item {
26 --delay: 0;
27 animation: fadeInUp 0.5s ease forwards;
28 animation-delay: calc(var(--delay) * 0.1s);
29}
30
31/* Set via inline style or JavaScript */
32/* style="--delay: 3" */
33
34/* Grid reveal */
35.grid-item {
36 opacity: 0;
37 animation: scaleIn 0.3s ease forwards;
38 animation-delay: calc(
39 (var(--row) * 0.1s) + (var(--col) * 0.1s)
40 );
41}Scroll-Triggered Animations#
1/* Using animation-timeline (modern browsers) */
2@keyframes scrollFade {
3 from {
4 opacity: 0;
5 transform: translateY(50px);
6 }
7 to {
8 opacity: 1;
9 transform: translateY(0);
10 }
11}
12
13.scroll-reveal {
14 animation: scrollFade linear;
15 animation-timeline: view();
16 animation-range: entry 0% cover 40%;
17}
18
19/* Fallback: use IntersectionObserver + class */
20.animate-on-scroll {
21 opacity: 0;
22 transform: translateY(30px);
23 transition: opacity 0.6s, transform 0.6s;
24}
25
26.animate-on-scroll.visible {
27 opacity: 1;
28 transform: translateY(0);
29}Interactive Animations#
1/* Hover animations */
2.hover-lift {
3 transition: transform 0.3s, box-shadow 0.3s;
4}
5
6.hover-lift:hover {
7 transform: translateY(-5px);
8 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
9}
10
11/* Click animations */
12.click-shrink:active {
13 transform: scale(0.95);
14 transition: transform 0.1s;
15}
16
17/* Focus animations */
18.focus-ring:focus {
19 outline: none;
20 animation: focusRing 0.3s ease-out;
21}
22
23@keyframes focusRing {
24 from {
25 box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.5);
26 }
27 to {
28 box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.5);
29 }
30}
31
32/* Ripple effect */
33.ripple {
34 position: relative;
35 overflow: hidden;
36}
37
38.ripple::after {
39 content: '';
40 position: absolute;
41 width: 100%;
42 height: 100%;
43 background: radial-gradient(circle, rgba(255,255,255,0.3) 0%, transparent 60%);
44 transform: scale(0);
45 opacity: 0;
46}
47
48.ripple:active::after {
49 animation: rippleEffect 0.6s ease-out;
50}
51
52@keyframes rippleEffect {
53 to {
54 transform: scale(4);
55 opacity: 0;
56 }
57}Performance Optimization#
1/* Animate only transform and opacity */
2.performant {
3 /* These trigger compositing only */
4 animation: slideIn 0.5s ease;
5}
6
7@keyframes slideIn {
8 from {
9 transform: translateX(-100%);
10 opacity: 0;
11 }
12 to {
13 transform: translateX(0);
14 opacity: 1;
15 }
16}
17
18/* Use will-change for complex animations */
19.complex-animation {
20 will-change: transform, opacity;
21}
22
23.complex-animation.done {
24 will-change: auto;
25}
26
27/* Reduce motion for accessibility */
28@media (prefers-reduced-motion: reduce) {
29 *,
30 *::before,
31 *::after {
32 animation-duration: 0.01ms !important;
33 animation-iteration-count: 1 !important;
34 transition-duration: 0.01ms !important;
35 }
36}
37
38/* Or provide alternative */
39@media (prefers-reduced-motion: reduce) {
40 .animated-element {
41 animation: none;
42 opacity: 1;
43 transform: none;
44 }
45}JavaScript Integration#
1/* Pause/play with class */
2.animation-paused {
3 animation-play-state: paused;
4}
5
6/* Trigger with class */
7.slide-in {
8 opacity: 0;
9 transform: translateX(-50px);
10}
11
12.slide-in.active {
13 animation: slideInAnimation 0.5s ease forwards;
14}
15
16/* Animation end handling */
17/*
18element.addEventListener('animationend', (e) => {
19 if (e.animationName === 'slideIn') {
20 element.classList.add('animation-complete');
21 }
22});
23*/Best Practices#
Performance:
✓ Animate transform and opacity
✓ Use will-change sparingly
✓ Avoid animating layout properties
✓ Test on lower-end devices
Accessibility:
✓ Respect prefers-reduced-motion
✓ Provide pause controls for loops
✓ Don't rely on animation for info
✓ Keep durations reasonable
Design:
✓ Use easing for natural motion
✓ Keep animations purposeful
✓ Maintain consistency
✓ Don't overuse animations
Code:
✓ Use CSS custom properties
✓ Create reusable keyframes
✓ Document animation intent
✓ Clean up will-change
Conclusion#
CSS animations create engaging user experiences. Use keyframes for complex multi-step animations, prioritize performance with transform and opacity, and always respect accessibility preferences. Start with purpose, keep animations subtle, and test across devices.