Animation Patterns & Examples
CSS animations power modern interactive web experiences. Proven patterns include loading spinners, bouncing elements, smooth page transitions, staggered list reveals, and attention-grabbing effects. This page covers real-world patterns, performance optimization, and how to combine animations with transitions for polished interactions.
Common animation patterns
CSS
/* 1. LOADING SPINNER */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* 2. PULSING DOT (breathing effect) */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.pulse {
width: 12px;
height: 12px;
background: #3498db;
border-radius: 50%;
animation: pulse 2s ease-in-out infinite;
}
/* 3. BOUNCING BALL */
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-30px); }
}
.bouncing-ball {
width: 20px;
height: 20px;
background: #e74c3c;
border-radius: 50%;
animation: bounce 0.8s ease infinite;
}
/* 4. FADE IN */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fade-in 1s ease forwards;
}
/* 5. SLIDE IN FROM LEFT */
@keyframes slide-in-left {
from { opacity: 0; transform: translateX(-50px); }
to { opacity: 1; transform: translateX(0); }
}
.slide-in-left {
animation: slide-in-left 0.6s ease forwards;
}
/* 6. SCALE UP */
@keyframes scale-up {
from { transform: scale(0.9); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.scale-up {
animation: scale-up 0.4s ease forwards;
}Staggered animations
CSS
/* List items cascade in with staggered delays */
@keyframes list-entrance {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.list-item {
animation: list-entrance 0.5s ease forwards;
}
.list-item:nth-child(1) { animation-delay: 0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
.list-item:nth-child(4) { animation-delay: 0.3s; }
.list-item:nth-child(5) { animation-delay: 0.4s; }
/* Grid stagger (diagonal wave) */
.grid-item {
animation: scale-up 0.4s ease forwards;
animation-delay: calc((var(--row) + var(--col)) * 0.1s);
/* Requires CSS custom properties from JavaScript */
}
/* Wave effect: each item delays based on position */
.wave-item {
animation: float-up 0.6s ease infinite;
}
@keyframes float-up {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.wave-item:nth-child(1) { animation-delay: 0s; }
.wave-item:nth-child(2) { animation-delay: 0.1s; }
.wave-item:nth-child(3) { animation-delay: 0.2s; }
.wave-item:nth-child(4) { animation-delay: 0.3s; }
.wave-item:nth-child(5) { animation-delay: 0.4s; }Multi-property animations
CSS
/* Card entrance with multiple properties */
@keyframes card-entrance {
from {
opacity: 0;
transform: scale(0.95) translateY(20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.card {
animation: card-entrance 0.6s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
/* Complex animation with key points */
@keyframes complex-motion {
0% {
opacity: 0;
transform: translateY(50px) scale(0.9);
}
40% {
opacity: 1;
transform: translateY(-5px) scale(1.02);
}
70% {
transform: translateY(2px) scale(0.99);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.element {
animation: complex-motion 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
}
/* Attention-grabbing shake */
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
.attention {
animation: shake 0.4s ease;
}
/* Highlight effect */
@keyframes highlight {
0% { background-color: #fff9e6; }
100% { background-color: transparent; }
}
.new-item {
animation: highlight 1s ease forwards;
}Interactive animations
CSS
/* Button hover animation */
.button {
background: #3498db;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.button:hover {
background: #2980b9;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.4);
}
/* Button with ripple effect */
@keyframes ripple {
to {
opacity: 0;
transform: scale(4);
}
}
.ripple-button::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
opacity: 1;
}
.ripple-button:active::after {
animation: ripple 0.6s ease-out;
}
/* Menu slide animation */
@keyframes menu-slide {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.menu.open {
animation: menu-slide 0.3s ease forwards;
}
/* Dropdown expand animation */
@keyframes dropdown-expand {
from {
opacity: 0;
max-height: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
max-height: 500px;
transform: translateY(0);
}
}
.dropdown.open {
animation: dropdown-expand 0.3s ease forwards;
}Loading and progress patterns
CSS
/* Animated dots loader */
@keyframes dot-pulse {
0%, 60%, 100% { opacity: 0.3; }
30% { opacity: 1; }
}
.loading-dots span {
animation: dot-pulse 1.4s ease-in-out infinite;
}
.loading-dots span:nth-child(2) { animation-delay: 0.2s; }
.loading-dots span:nth-child(3) { animation-delay: 0.4s; }
/* Progress bar animation */
@keyframes progress {
0% { width: 0%; }
50% { width: 80%; }
100% { width: 100%; }
}
.progress-bar {
height: 4px;
background: #3498db;
animation: progress 2s ease forwards;
}
/* Infinite progress (indeterminate) */
@keyframes indeterminate {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
.progress-indeterminate::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 30%;
background: linear-gradient(90deg, transparent, #3498db, transparent);
animation: indeterminate 1.5s ease-in-out infinite;
}
/* Skeleton loading animation */
@keyframes skeleton-loading {
0% { background-position: -1000px 0; }
100% { background-position: 1000px 0; }
}
.skeleton {
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 1000px 100%;
animation: skeleton-loading 2s infinite;
}Notification and toast animations
CSS
/* Toast slide-up entrance */
@keyframes toast-enter {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.toast {
animation: toast-enter 0.3s ease forwards;
animation-fill-mode: forwards;
}
/* Toast slide-down exit */
@keyframes toast-exit {
from {
transform: translateY(0);
opacity: 1;
}
to {
transform: translateY(100%);
opacity: 0;
}
}
.toast.removing {
animation: toast-exit 0.3s ease forwards;
}
/* Notification bell shake */
@keyframes bell-shake {
0%, 100% { transform: rotate(0deg); }
10%, 30% { transform: rotate(-10deg); }
20%, 40%, 60%, 80% { transform: rotate(10deg); }
50% { transform: rotate(-10deg); }
70% { transform: rotate(10deg); }
90% { transform: rotate(-10deg); }
}
.notification-bell.alert {
animation: bell-shake 0.5s ease;
}
/* Badge pop animation */
@keyframes badge-pop {
0% {
transform: scale(0);
opacity: 0;
}
70% {
transform: scale(1.2);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.badge.new {
animation: badge-pop 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}Performance optimization
CSS
/* Use transform for smooth 60fps animations */
.smooth {
animation: move 1s ease;
}
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
/* Avoid animating layout properties */
.avoid {
animation: bad-move 1s ease;
}
@keyframes bad-move {
from { left: 0; }
to { left: 100px; }
}
/* Use will-change for complex animations */
.optimized {
will-change: transform, opacity;
animation: complex-anim 2s ease infinite;
}
/* Keep animations simple */
.simple-anim {
animation: fade 1s ease;
}
@keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
/* Limit simultaneous animations */
.limited {
animation:
fade 0.5s ease forwards,
scale 0.5s ease forwards;
/* 2 animations max */
}
/* Pause animations off-screen */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
}
}Combining animations and transitions
CSS
/* Animation for entrance, transition for interaction */
.element {
/* Entrance animation */
animation: fade-in 0.5s ease forwards;
/* Interaction transition */
transition: background 0.3s ease, transform 0.2s ease;
}
.element:hover {
background: #f0f0f0;
transform: scale(1.05);
}
/* Entrance animation with delayed transition */
@keyframes card-entrance {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
.card {
animation: card-entrance 0.5s ease forwards;
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.card:hover {
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
transform: scale(1.02);
}
/* Multiple entrance animations with stagger */
.list-item {
animation: list-enter 0.6s ease forwards;
transition: all 0.3s ease;
}
.list-item:nth-child(1) { animation-delay: 0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
.list-item:hover {
background: #f5f5f5;
padding-left: 10px;
}
@keyframes list-enter {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}Note
Proven animation patterns: spinners (spin 1s linear infinite), pulsing dots (pulse 2s ease-in-out infinite with staggered delays), bouncing (bounce 0.8s ease infinite alternate), fade-in (0.5s ease forwards), staggered lists (0.1s delay increments). Key principles: use transform for performance, keep under 1s for feedback, limit to 2-3 concurrent animations, respect prefers-reduced-motion. Combine animations (entrance) with transitions (interaction) for polished UX.
Next
Scroll-driven animations: [Scroll-Driven Animations](/css/scroll-driven-animations).