Animation

Animation comparisons

13 old vs modern animation CSS techniques, side by side.

Browser compatibility:
Animation
Intermediate

Random values per element without JavaScript

Old // JS: set inline style per el
el.style.setProperty('--r',
  Math.random() * 30 - 15 + 'deg')
Modern rotate: random(-15deg, 15deg);
animation-delay: random(0s, 1s);
/* per element, no JS */
see modern →
Animation
Intermediate

Motion path animation without JavaScript

Old // GSAP motionPath plugin
gsap.to('.el', { motionPath: { path: '#svg-path' } })
/* requires GSAP + SVG markup */
Modern offset-path: path("M 0 0 C 150 -100 300 100 450 0");
offset-distance: 0%;
/* animate to 100% — pure CSS */
see modern →
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