Animation comparisons
11 old vs modern animation CSS techniques, side by side.
Browser compatibility:
Animation
Beginner
Reduced motion without JavaScript detection
Old
const mq = window.matchMedia('(prefers-reduced-motion)');
if (mq.matches) el.style.animation = 'none';
if (mq.matches) el.style.animation = 'none';
Modern
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.01ms !important; }
}
see modern →
* { animation-duration: 0.01ms !important; }
}
Animation
Intermediate
Custom easing curves without cubic-bezier guessing
Old
/* JS animation library */
anime({ targets: el,
easing: 'easeOutBounce' });
anime({ targets: el,
easing: 'easeOutBounce' });
Modern
transition: transform 1s
linear(0, 1.2, 0.9, 1.05, 1);
/* bounce — no JS needed */
see modern →
linear(0, 1.2, 0.9, 1.05, 1);
/* bounce — no JS needed */
Animation
Beginner
Smooth height auto animations without JavaScript
Old
// measure, set px, then snap to auto
el.style.height = el.scrollHeight + 'px';
el.addEventListener('transitionend', ...)
el.style.height = el.scrollHeight + 'px';
el.addEventListener('transitionend', ...)
Modern
:root { interpolate-size: allow-keywords; }
.accordion { height: 0; overflow: hidden;
transition: height .3s ease; }
.accordion.open { height: auto; }
see modern →
.accordion { height: 0; overflow: hidden;
transition: height .3s ease; }
.accordion.open { height: auto; }
Animation
Intermediate
Sticky & snapped element styling without JavaScript
Old
window.addEventListener(
'scroll', () => {
/* check position */
});
'scroll', () => {
/* check position */
});
Modern
@container scroll-state(
stuck: top
) {
.header { ... }
}
see modern →
stuck: top
) {
.header { ... }
}
Animation
Advanced
Responsive clip paths without SVG
Old
.shape {
clip-path: path(
'M0 200 L100 0...'
);
}
clip-path: path(
'M0 200 L100 0...'
);
}
Modern
.shape {
clip-path: shape(
from 0% 100%, ...
);
}
see modern →
clip-path: shape(
from 0% 100%, ...
);
}
Animation
Intermediate
Staggered animations without nth-child hacks
Old
li:nth-child(1) { --i: 0; }
li:nth-child(2) { --i: 1; }
li:nth-child(3) { --i: 2; }
/* repeat for every item… */
li:nth-child(2) { --i: 1; }
li:nth-child(3) { --i: 2; }
/* repeat for every item… */
Modern
li {
transition-delay:
calc(0.1s * (sibling-index() - 1));
}
see modern →
transition-delay:
calc(0.1s * (sibling-index() - 1));
}
Animation
Beginner
Independent transforms without the shorthand
Old
.icon { transform: translateX(10px) rotate(45deg) scale(1.2); }
/* change one = rewrite all */
/* change one = rewrite all */
Modern
.icon {
translate: 10px 0;
rotate: 45deg;
scale: 1.2;
}
/* animate any one without touching the rest */
see modern →
translate: 10px 0;
rotate: 45deg;
scale: 1.2;
}
/* animate any one without touching the rest */
Animation
Intermediate
Animating display none without workarounds
Old
// wait for transitionend then display:none
el.addEventListener('transitionend', …)
visibility + opacity + pointer-events
el.addEventListener('transitionend', …)
visibility + opacity + pointer-events
Modern
.panel { transition: opacity .2s, overlay .2s;
transition-behavior: allow-discrete; }
.panel.hidden { opacity: 0; display: none; }
/* no JS wait or visibility hack */
see modern →
transition-behavior: allow-discrete; }
.panel.hidden { opacity: 0; display: none; }
/* no JS wait or visibility hack */
Animation
Intermediate
Entry animations without JavaScript timing
Old
// add class after paint
requestAnimationFrame(() => {
el.classList.add('visible');
});
requestAnimationFrame(() => {
el.classList.add('visible');
});
Modern
.card { transition: opacity .3s, transform .3s; }
.card { @starting-style { opacity: 0; transform: translateY(10px); } }
/* no rAF/setTimeout */
see modern →
.card { @starting-style { opacity: 0; transform: translateY(10px); } }
/* no rAF/setTimeout */
Animation
Advanced
Page transitions without a framework
Old
// Barba.js or React Transition Group
Barba.init({ … })
transition hooks + duration state
Barba.init({ … })
transition hooks + duration state
Modern
document.startViewTransition(() => updateDOM());
.hero { view-transition-name: hero; }
/* no Barba, no React TG */
see modern →
.hero { view-transition-name: hero; }
/* no Barba, no React TG */
Animation
Advanced
Scroll-linked animations without a library
Old
// JS + IntersectionObserver
observer.observe(el)
el.style.opacity = …
observer.observe(el)
el.style.opacity = …
Modern
animation-timeline: view();
animation-range: entry;
/* pure CSS, GPU-accelerated */
see modern →
animation-range: entry;
/* pure CSS, GPU-accelerated */