CSS animations enable complex motion sequences using keyframes. Here's how to create engaging animations.
Basic Keyframes#
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 1s ease-in-out;
14}
15
16/* Multiple steps */
17@keyframes bounce {
18 0% {
19 transform: translateY(0);
20 }
21 50% {
22 transform: translateY(-20px);
23 }
24 100% {
25 transform: translateY(0);
26 }
27}
28
29.bounce {
30 animation: bounce 0.5s ease-in-out;
31}Animation Properties#
1.animated {
2 /* Shorthand */
3 animation: slideIn 1s ease-out 0.5s infinite alternate both;
4
5 /* Individual properties */
6 animation-name: slideIn;
7 animation-duration: 1s;
8 animation-timing-function: ease-out;
9 animation-delay: 0.5s;
10 animation-iteration-count: infinite;
11 animation-direction: alternate;
12 animation-fill-mode: both;
13 animation-play-state: running;
14}Timing Functions#
1/* Built-in timing functions */
2.linear {
3 animation-timing-function: linear;
4}
5.ease {
6 animation-timing-function: ease;
7}
8.ease-in {
9 animation-timing-function: ease-in;
10}
11.ease-out {
12 animation-timing-function: ease-out;
13}
14.ease-in-out {
15 animation-timing-function: ease-in-out;
16}
17
18/* Custom cubic-bezier */
19.custom {
20 animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
21}
22
23/* Steps */
24.steps {
25 animation-timing-function: steps(4, end);
26}Fill Modes#
1/* none - no styles before/after */
2.fill-none {
3 animation-fill-mode: none;
4}
5
6/* forwards - keep final keyframe */
7.fill-forwards {
8 animation-fill-mode: forwards;
9}
10
11/* backwards - apply first keyframe during delay */
12.fill-backwards {
13 animation-fill-mode: backwards;
14}
15
16/* both - combine forwards and backwards */
17.fill-both {
18 animation-fill-mode: both;
19}Direction Options#
1/* normal - play forward */
2.normal {
3 animation-direction: normal;
4}
5
6/* reverse - play backward */
7.reverse {
8 animation-direction: reverse;
9}
10
11/* alternate - forward then backward */
12.alternate {
13 animation-direction: alternate;
14}
15
16/* alternate-reverse - backward then forward */
17.alternate-reverse {
18 animation-direction: alternate-reverse;
19}Practical Animations#
1/* Fade and slide */
2@keyframes fadeSlideUp {
3 from {
4 opacity: 0;
5 transform: translateY(30px);
6 }
7 to {
8 opacity: 1;
9 transform: translateY(0);
10 }
11}
12
13.card {
14 animation: fadeSlideUp 0.5s ease-out forwards;
15}
16
17/* Staggered animation */
18.card:nth-child(1) { animation-delay: 0.1s; }
19.card:nth-child(2) { animation-delay: 0.2s; }
20.card:nth-child(3) { animation-delay: 0.3s; }
21.card:nth-child(4) { animation-delay: 0.4s; }
22
23/* Pulse effect */
24@keyframes pulse {
25 0%, 100% {
26 transform: scale(1);
27 }
28 50% {
29 transform: scale(1.05);
30 }
31}
32
33.pulse {
34 animation: pulse 2s ease-in-out infinite;
35}Loading Spinners#
1/* Rotating 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-color: #3b82f6;
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 }
22 40% {
23 transform: scale(1);
24 }
25}
26
27.loading-dots {
28 display: flex;
29 gap: 8px;
30}
31
32.loading-dots span {
33 width: 12px;
34 height: 12px;
35 background: #3b82f6;
36 border-radius: 50%;
37 animation: dotPulse 1.4s ease-in-out infinite;
38}
39
40.loading-dots span:nth-child(1) { animation-delay: -0.32s; }
41.loading-dots span:nth-child(2) { animation-delay: -0.16s; }Attention Seekers#
1/* Shake */
2@keyframes shake {
3 0%, 100% { transform: translateX(0); }
4 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
5 20%, 40%, 60%, 80% { transform: translateX(5px); }
6}
7
8.shake {
9 animation: shake 0.5s ease-in-out;
10}
11
12/* Wiggle */
13@keyframes wiggle {
14 0%, 100% { transform: rotate(0deg); }
15 25% { transform: rotate(-5deg); }
16 75% { transform: rotate(5deg); }
17}
18
19.wiggle {
20 animation: wiggle 0.3s ease-in-out;
21}
22
23/* Flash */
24@keyframes flash {
25 0%, 50%, 100% { opacity: 1; }
26 25%, 75% { opacity: 0; }
27}
28
29.flash {
30 animation: flash 1s ease-in-out;
31}Entrance Animations#
1/* Zoom in */
2@keyframes zoomIn {
3 from {
4 opacity: 0;
5 transform: scale(0.5);
6 }
7 to {
8 opacity: 1;
9 transform: scale(1);
10 }
11}
12
13/* Slide from directions */
14@keyframes slideInLeft {
15 from {
16 opacity: 0;
17 transform: translateX(-100%);
18 }
19 to {
20 opacity: 1;
21 transform: translateX(0);
22 }
23}
24
25@keyframes slideInRight {
26 from {
27 opacity: 0;
28 transform: translateX(100%);
29 }
30 to {
31 opacity: 1;
32 transform: translateX(0);
33 }
34}
35
36/* Flip */
37@keyframes flipIn {
38 from {
39 opacity: 0;
40 transform: perspective(400px) rotateY(90deg);
41 }
42 to {
43 opacity: 1;
44 transform: perspective(400px) rotateY(0);
45 }
46}Exit Animations#
1/* Fade out */
2@keyframes fadeOut {
3 to {
4 opacity: 0;
5 }
6}
7
8/* Zoom out */
9@keyframes zoomOut {
10 to {
11 opacity: 0;
12 transform: scale(0.5);
13 }
14}
15
16/* Slide out */
17@keyframes slideOutUp {
18 to {
19 opacity: 0;
20 transform: translateY(-100%);
21 }
22}Multiple Animations#
1/* Apply multiple animations */
2.multi-animated {
3 animation:
4 fadeIn 0.5s ease-out,
5 slideUp 0.5s ease-out,
6 pulse 2s ease-in-out 0.5s infinite;
7}
8
9/* Sequenced with delay */
10.sequence {
11 animation:
12 fadeIn 0.3s ease-out forwards,
13 scale 0.3s ease-out 0.3s forwards,
14 glow 0.5s ease-in-out 0.6s forwards;
15}Animation Events (JavaScript)#
1const element = document.querySelector('.animated');
2
3element.addEventListener('animationstart', (e) => {
4 console.log('Animation started:', e.animationName);
5});
6
7element.addEventListener('animationend', (e) => {
8 console.log('Animation ended:', e.animationName);
9});
10
11element.addEventListener('animationiteration', (e) => {
12 console.log('Animation iteration:', e.animationName);
13});
14
15// Trigger animation programmatically
16function triggerAnimation(el, animationClass) {
17 el.classList.remove(animationClass);
18 void el.offsetWidth; // Force reflow
19 el.classList.add(animationClass);
20}Performance Optimization#
1/* Use transform and opacity for best performance */
2@keyframes optimized {
3 from {
4 opacity: 0;
5 transform: translate3d(0, 20px, 0);
6 }
7 to {
8 opacity: 1;
9 transform: translate3d(0, 0, 0);
10 }
11}
12
13/* Promote to own layer */
14.gpu-accelerated {
15 will-change: transform, opacity;
16 transform: translateZ(0);
17}
18
19/* Avoid animating these properties */
20.slow-animation {
21 /* Avoid: width, height, top, left, margin, padding */
22 /* Use transform instead */
23}Reduced Motion#
1/* Respect user preferences */
2@media (prefers-reduced-motion: reduce) {
3 *,
4 *::before,
5 *::after {
6 animation-duration: 0.01ms !important;
7 animation-iteration-count: 1 !important;
8 transition-duration: 0.01ms !important;
9 }
10}
11
12/* Or provide alternative */
13@media (prefers-reduced-motion: reduce) {
14 .animated {
15 animation: none;
16 opacity: 1;
17 transform: none;
18 }
19}Best Practices#
Performance:
✓ Animate transform and opacity
✓ Use will-change sparingly
✓ Avoid layout thrashing
✓ Use GPU acceleration
Timing:
✓ Keep durations short (200-500ms)
✓ Use appropriate easing
✓ Stagger multiple elements
✓ Add subtle delays
Accessibility:
✓ Respect prefers-reduced-motion
✓ Don't rely solely on animation
✓ Provide pause controls
✓ Avoid rapid flashing
Avoid:
✗ Animating layout properties
✗ Too many simultaneous animations
✗ Infinite animations without purpose
✗ Motion sickness triggers
Conclusion#
CSS animations with keyframes enable rich motion design without JavaScript. Use transform and opacity for best performance, respect user preferences with prefers-reduced-motion, and keep durations short for better UX. Combine multiple animations with delays for sophisticated sequences, and always provide meaningful motion that enhances rather than distracts.