Back to Blog
CSSAnimationsKeyframesMotion

CSS Animations and Keyframes

Master CSS animations with keyframes. From basics to complex multi-step animations.

B
Bootspring Team
Engineering
October 12, 2020
7 min read

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.

Share this article

Help spread the word about Bootspring