Animation

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';
Modern @media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; }
}
see modern →
Animation
Intermediate

Custom easing curves without cubic-bezier guessing

Old /* JS animation library */
anime({ targets: el,
  easing: 'easeOutBounce' });
Modern transition: transform 1s
  linear(0, 1.2, 0.9, 1.05, 1);
/* bounce — no JS needed */
see modern →
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', ...)
Modern :root { interpolate-size: allow-keywords; }
.accordion { height: 0; overflow: hidden;
  transition: height .3s ease; }
.accordion.open { height: auto; }
see modern →
Animation
Intermediate

Sticky & snapped element styling without JavaScript

Old window.addEventListener(
  'scroll', () => {
    /* check position */
});
Modern @container scroll-state(
  stuck: top
) {
  .header { ... }
}
see modern →
Animation
Advanced

Responsive clip paths without SVG

Old .shape {
  clip-path: path(
    'M0 200 L100 0...'
  );
}
Modern .shape {
  clip-path: shape(
    from 0% 100%, ...
  );
}
see modern →
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… */
Modern li {
  transition-delay:
    calc(0.1s * (sibling-index() - 1));
}
see modern →
Animation
Beginner

Independent transforms without the shorthand

Old .icon { transform: translateX(10px) rotate(45deg) scale(1.2); }
/* change one = rewrite all */
Modern .icon {
  translate: 10px 0;
  rotate: 45deg;
  scale: 1.2;
}
/* animate any one without touching the rest */
see modern →
Animation
Intermediate

Animating display none without workarounds

Old // wait for transitionend then display:none
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 →
Animation
Intermediate

Entry animations without JavaScript timing

Old // add class after paint
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 →
Animation
Advanced

Page transitions without a framework

Old // Barba.js or React Transition Group
Barba.init({ … })
transition hooks + duration state
Modern document.startViewTransition(() => updateDOM());
.hero { view-transition-name: hero; }
/* no Barba, no React TG */
see modern →
Animation
Advanced

Scroll-linked animations without a library

Old // JS + IntersectionObserver
observer.observe(el)
el.style.opacity = …
Modern animation-timeline: view();
animation-range: entry;
/* pure CSS, GPU-accelerated */
see modern →

Other categories

New CSS drops.

Join 600+ readers who've survived clearfix hacks.

ESC