Animation comparisons
13 old vs modern animation CSS techniques, side by side.
Browser compatibility:
Random values per element without JavaScript
Limited availability Old
// JS: randomise per element at runtimedocument.querySelectorAll('.card').forEach(el => { const r = Math.random() * 30 - 15; el.style.setProperty('--rotate', r + 'deg'); const d = Math.random(); el.style.setProperty('--delay', d + 's');});/* CSS reads the inline vars */.card { rotate: var(--rotate); animation-delay: var(--delay);} Modern
.card { rotate: random(-15deg, 15deg); animation-delay: random(0s, 1s);} Motion path animation without JavaScript
Widely available Old
<!-- SVG in HTML --><svg><path id="track" d="M 0 0 C 150 -100 300 100 450 0"/></svg><div class="ball"></div>// gsap + motionPath plugin requiredgsap.to('.ball', { duration: 2, ease: 'none', motionPath: { path: '#track', autoRotate: true }}); Modern
@keyframes along-path { from { offset-distance: 0%; } to { offset-distance: 100%; }}.ball { offset-path: path("M 0 0 C 150 -100 300 100 450 0"); offset-distance: 0%; offset-rotate: auto; animation: along-path 2s linear infinite;} Reduced motion without JavaScript detection
Widely available Old
// JS: check OS preference, disable animations manuallyconst mq = window.matchMedia('(prefers-reduced-motion: reduce)');if (mq.matches) { document.querySelectorAll('.animated').forEach( el => el.style.animation = 'none' );} Modern
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; }} Custom easing curves without cubic-bezier guessing
Newly available Old
// JS animation library for bounce/spring easinganime({ targets: el, translateX: 300, easing: 'spring(1, 80, 10, 0)'}); Modern
.el { transition: transform 0.6s linear(0, 1.2 60%, 0.9, 1.05, 1);} Smooth height auto animations without JavaScript
Newly available Old
.accordion { overflow: hidden;}// JS: measure, set px height, then snap to autofunction open(el) { el.style.height = el.scrollHeight + 'px'; el.addEventListener('transitionend', () => { el.style.height = 'auto'; }, { once: true }); } Modern
:root { interpolate-size: allow-keywords;}.accordion { height: 0; overflow: hidden; transition: height .3s ease;}.accordion.open { height: auto;} Sticky & snapped element styling without JavaScript
Limited availability Old
/* JS scroll listener approach */const header = document .querySelector('.header');const offset = header .offsetTop;window.addEventListener( 'scroll', () => { header.classList .toggle('stuck', window.scrollY >= offset);}); Modern
.header-wrap { container-type: scroll-state;}@container scroll-state( /* [!code ++] */stuck: top /* [!code ++] */) { .header { box-shadow: 0 2px 8px #0002; }} Responsive clip paths without SVG
Limited availability Old
.shape { clip-path: path( 'M0 200 L100 0 L200 200 Z' );} Modern
.shape { clip-path: shape( from 0% 100%, line to 50% 0%, line to 100% 100% );} Staggered animations without nth-child hacks
Limited availability Old
/* Manual index per nth-child */li:nth-child(1) { --i: 0;}li:nth-child(2) { --i: 1;}li:nth-child(3) { --i: 2;}li:nth-child(4) { --i: 3;}/* … repeat for every item … */li { transition-delay: calc(0.1s * var(--i));} Modern
li { transition: opacity .25s ease, translate .25s ease; transition-delay: calc(0.1s * (sibling-index() - 1));} Independent transforms without the shorthand
Widely available Old
.icon { transform: translateX(10px) rotate(45deg) scale(1.2);}.icon:hover { transform: translateX(10px) rotate(90deg) scale(1.2); Modern
.icon { translate: 10px 0; rotate: 45deg; scale: 1.2;}.icon:hover { rotate: 90deg;} Animating display none without workarounds
Newly available Old
.panel { transition: opacity .2s;}.panel.hidden { opacity: 0; visibility: hidden;}// JS: after transition, set display:none and pointer-eventsel.addEventListener('transitionend', () => { el.style.display = 'none'; }); Modern
.panel { transition: opacity .2s, overlay .2s allow-discrete; transition-behavior: allow-discrete;}.panel.hidden { opacity: 0; display: none;} Entry animations without JavaScript timing
Newly available Old
.card { opacity: 0; transform: translateY(10px);}.card.visible { opacity: 1; transform: none;}// JS: must run after paint or transition won't run requestAnimationFrame(() => { requestAnimationFrame(() => { el.classList.add('visible'); });}); Modern
.card { transition: opacity .3s, transform .3s; @starting-style { opacity: 0; transform: translateY(10px); }} Page transitions without a framework
Newly available Old
// barba.js or custom router transitionsimport Barba from '@barba/core';Barba.init({ transitions: [{ leave: ({ current }) => gsap.to(current.container, { opacity: 0 }), enter: ({ next }) => gsap.from(next.container, { opacity: 0 }) }]}); Modern
// JS: wrap DOM updatedocument.startViewTransition(() => { document.body.innerHTML = newContent;});.hero { view-transition-name: hero;}/* Optional: ::view-transition-old/new(hero) */ Scroll-linked animations without a library
Limited availability Old
// scroll-reveal.jsconst obs = new IntersectionObserver( (entries) => { entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); }); });document.querySelectorAll('.reveal') .forEach(el => obs.observe(el)); Modern
@keyframes reveal { from { opacity: 0; translate: 0 40px; } to { opacity: 1; translate: 0 0; }}.reveal { animation: reveal linear both; animation-timeline: view(); animation-range: entry 0% entry 100%;}