Back to Blog
CSSAnimationsKeyframesDesign

CSS Animations Guide

Master CSS animations with keyframes for engaging user experiences.

B
Bootspring Team
Engineering
August 8, 2018
7 min read

CSS animations create engaging experiences with keyframes and animation properties. Here's how to master them.

Basic Keyframes#

1/* Define animation with @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/* Percentage keyframes */ 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/* Multiple keyframes */ 30@keyframes pulse { 31 0%, 100% { 32 transform: scale(1); 33 } 34 50% { 35 transform: scale(1.1); 36 } 37}

Animation Properties#

1/* Individual properties */ 2.animated { 3 animation-name: slideIn; 4 animation-duration: 1s; 5 animation-timing-function: ease-out; 6 animation-delay: 0.5s; 7 animation-iteration-count: 3; 8 animation-direction: alternate; 9 animation-fill-mode: forwards; 10 animation-play-state: running; 11} 12 13/* Shorthand */ 14.animated { 15 animation: slideIn 1s ease-out 0.5s 3 alternate forwards; 16 /* name | duration | timing | delay | iterations | direction | fill-mode */ 17} 18 19/* Multiple animations */ 20.multi { 21 animation: 22 fadeIn 1s ease-out, 23 slideUp 0.5s ease-in-out, 24 bounce 2s infinite; 25}

Timing Functions#

1/* Built-in timing functions */ 2.timing { 3 animation-timing-function: linear; 4 animation-timing-function: ease; 5 animation-timing-function: ease-in; 6 animation-timing-function: ease-out; 7 animation-timing-function: ease-in-out; 8} 9 10/* Steps for frame-by-frame */ 11.steps { 12 animation-timing-function: steps(4, start); 13 animation-timing-function: steps(4, end); 14 animation-timing-function: step-start; 15 animation-timing-function: step-end; 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/* Common cubic-bezier presets */ 24.ease-out-back { 25 animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); 26} 27 28.ease-in-out-back { 29 animation-timing-function: cubic-bezier(0.68, -0.6, 0.32, 1.6); 30}

Iteration and Direction#

1/* Iteration count */ 2.iterate { 3 animation-iteration-count: 1; /* Play once */ 4 animation-iteration-count: 3; /* Play 3 times */ 5 animation-iteration-count: infinite; /* Loop forever */ 6 animation-iteration-count: 2.5; /* Play 2.5 times */ 7} 8 9/* Direction */ 10.direction { 11 animation-direction: normal; /* 0% -> 100% */ 12 animation-direction: reverse; /* 100% -> 0% */ 13 animation-direction: alternate; /* 0% -> 100% -> 0% -> ... */ 14 animation-direction: alternate-reverse; /* 100% -> 0% -> 100% -> ... */ 15} 16 17/* Infinite bounce */ 18.bounce { 19 animation: bounce 0.5s ease-in-out infinite alternate; 20}

Fill Mode#

1/* Fill mode controls state before/after animation */ 2.fill { 3 animation-fill-mode: none; /* Default - no styles applied */ 4 animation-fill-mode: forwards; /* Keep end state */ 5 animation-fill-mode: backwards; /* Apply start state during delay */ 6 animation-fill-mode: both; /* Both forwards and backwards */ 7} 8 9/* Example: Fade in and stay visible */ 10@keyframes fadeIn { 11 from { opacity: 0; } 12 to { opacity: 1; } 13} 14 15.fade-in { 16 opacity: 0; /* Initial state */ 17 animation: fadeIn 1s ease-out forwards; 18 /* forwards keeps opacity: 1 after animation */ 19} 20 21/* Apply start state during delay */ 22.delayed { 23 animation: fadeIn 1s ease-out 2s backwards; 24 /* opacity: 0 applied during 2s delay */ 25}

Play State#

1/* Control animation playback */ 2.controllable { 3 animation: spin 2s linear infinite; 4 animation-play-state: running; 5} 6 7.controllable:hover { 8 animation-play-state: paused; 9} 10 11/* Pause on hover */ 12.pause-on-hover { 13 animation: rotate 3s linear infinite; 14} 15 16.pause-on-hover:hover { 17 animation-play-state: paused; 18}

Common Animations#

1/* Fade In */ 2@keyframes fadeIn { 3 from { opacity: 0; } 4 to { opacity: 1; } 5} 6 7/* Slide In */ 8@keyframes slideInLeft { 9 from { 10 transform: translateX(-100%); 11 opacity: 0; 12 } 13 to { 14 transform: translateX(0); 15 opacity: 1; 16 } 17} 18 19/* Scale */ 20@keyframes scaleUp { 21 from { transform: scale(0); } 22 to { transform: scale(1); } 23} 24 25/* Rotate */ 26@keyframes spin { 27 from { transform: rotate(0deg); } 28 to { transform: rotate(360deg); } 29} 30 31/* Shake */ 32@keyframes shake { 33 0%, 100% { transform: translateX(0); } 34 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); } 35 20%, 40%, 60%, 80% { transform: translateX(5px); } 36} 37 38/* Pulse */ 39@keyframes pulse { 40 0% { transform: scale(1); } 41 50% { transform: scale(1.05); } 42 100% { transform: scale(1); } 43} 44 45/* Bounce */ 46@keyframes bounce { 47 0%, 20%, 50%, 80%, 100% { 48 transform: translateY(0); 49 } 50 40% { 51 transform: translateY(-30px); 52 } 53 60% { 54 transform: translateY(-15px); 55 } 56}

Loading Spinners#

1/* Simple spinner */ 2@keyframes spin { 3 to { transform: rotate(360deg); } 4} 5 6.spinner { 7 width: 40px; 8 height: 40px; 9 border: 4px solid #f3f3f3; 10 border-top: 4px solid #3498db; 11 border-radius: 50%; 12 animation: spin 1s linear infinite; 13} 14 15/* Dots loading */ 16@keyframes dotPulse { 17 0%, 80%, 100% { 18 transform: scale(0); 19 opacity: 0; 20 } 21 40% { 22 transform: scale(1); 23 opacity: 1; 24 } 25} 26 27.dots { 28 display: flex; 29 gap: 8px; 30} 31 32.dot { 33 width: 12px; 34 height: 12px; 35 background: #3498db; 36 border-radius: 50%; 37 animation: dotPulse 1.4s ease-in-out infinite; 38} 39 40.dot:nth-child(1) { animation-delay: -0.32s; } 41.dot:nth-child(2) { animation-delay: -0.16s; } 42.dot:nth-child(3) { animation-delay: 0s; } 43 44/* Progress bar */ 45@keyframes progress { 46 0% { 47 width: 0; 48 background-position: 0 0; 49 } 50 100% { 51 width: 100%; 52 background-position: 50px 0; 53 } 54} 55 56.progress-bar { 57 height: 4px; 58 background: linear-gradient( 59 45deg, 60 #3498db 25%, 61 #2980b9 25%, 62 #2980b9 50%, 63 #3498db 50%, 64 #3498db 75%, 65 #2980b9 75% 66 ); 67 background-size: 50px 50px; 68 animation: progress 2s linear infinite; 69}

Entrance Animations#

1/* Staggered entrance */ 2.stagger > * { 3 opacity: 0; 4 animation: fadeInUp 0.5s ease-out forwards; 5} 6 7.stagger > *:nth-child(1) { animation-delay: 0.1s; } 8.stagger > *:nth-child(2) { animation-delay: 0.2s; } 9.stagger > *:nth-child(3) { animation-delay: 0.3s; } 10.stagger > *:nth-child(4) { animation-delay: 0.4s; } 11 12@keyframes fadeInUp { 13 from { 14 opacity: 0; 15 transform: translateY(20px); 16 } 17 to { 18 opacity: 1; 19 transform: translateY(0); 20 } 21} 22 23/* Using CSS custom properties */ 24.stagger-item { 25 animation: fadeInUp 0.5s ease-out forwards; 26 animation-delay: calc(var(--i, 0) * 0.1s); 27} 28 29/* In HTML: style="--i: 0", style="--i: 1", etc. */

Attention Seekers#

1/* Wiggle */ 2@keyframes wiggle { 3 0%, 100% { transform: rotate(0); } 4 25% { transform: rotate(-5deg); } 5 75% { transform: rotate(5deg); } 6} 7 8.wiggle { 9 animation: wiggle 0.3s ease-in-out 3; 10} 11 12/* Heartbeat */ 13@keyframes heartbeat { 14 0% { transform: scale(1); } 15 14% { transform: scale(1.3); } 16 28% { transform: scale(1); } 17 42% { transform: scale(1.3); } 18 70% { transform: scale(1); } 19} 20 21.heartbeat { 22 animation: heartbeat 1.5s ease-in-out infinite; 23} 24 25/* Flash */ 26@keyframes flash { 27 0%, 50%, 100% { opacity: 1; } 28 25%, 75% { opacity: 0; } 29} 30 31.flash { 32 animation: flash 1s ease-in-out infinite; 33}

Performance#

1/* GPU-accelerated properties */ 2.performant { 3 /* Transform and opacity are GPU-accelerated */ 4 transform: translateX(0); 5 opacity: 1; 6 7 /* Hint to browser */ 8 will-change: transform, opacity; 9} 10 11/* Avoid animating these */ 12.avoid { 13 /* Layout-triggering (expensive) */ 14 animation: badAnimation 1s; 15} 16 17@keyframes badAnimation { 18 /* DON'T animate these */ 19 from { 20 width: 100px; 21 height: 100px; 22 margin: 0; 23 padding: 0; 24 top: 0; 25 left: 0; 26 } 27 to { 28 width: 200px; 29 height: 200px; 30 margin: 20px; 31 padding: 20px; 32 top: 100px; 33 left: 100px; 34 } 35} 36 37/* DO this instead */ 38@keyframes goodAnimation { 39 from { 40 transform: translate(0, 0) scale(1); 41 } 42 to { 43 transform: translate(100px, 100px) scale(2); 44 } 45}

Reduced Motion#

1/* Respect user preference */ 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/* Alternative subtle animation */ 13@media (prefers-reduced-motion: reduce) { 14 .animated { 15 animation: none; 16 opacity: 1; /* Show final state */ 17 } 18} 19 20/* Provide motion-safe alternative */ 21.element { 22 /* Reduced motion version */ 23 opacity: 1; 24 transform: none; 25} 26 27@media (prefers-reduced-motion: no-preference) { 28 .element { 29 animation: fadeIn 0.5s ease-out; 30 } 31}

Best Practices#

Performance: ✓ Animate transform and opacity ✓ Use will-change sparingly ✓ Keep animations short (< 500ms for UI) ✓ Test on low-end devices Accessibility: ✓ Respect prefers-reduced-motion ✓ Avoid flashing content ✓ Don't animate essential content ✓ Provide static alternatives UX: ✓ Use animation purposefully ✓ Keep animations subtle ✓ Match animation to content ✓ Consider mobile users Avoid: ✗ Animating layout properties ✗ Too many simultaneous animations ✗ Infinite animations without purpose ✗ Blocking user interaction

Conclusion#

CSS animations bring interfaces to life with keyframes and animation properties. Use GPU-accelerated properties (transform, opacity) for smooth performance. Respect user preferences with prefers-reduced-motion. Keep animations purposeful and subtle - they should enhance, not distract from, the user experience. Master timing functions for natural-feeling motion and use fill-mode to control animation state.

Share this article

Help spread the word about Bootspring