Skip to main content

CSS Animations vs JavaScript Animations

Answer​

Both CSS and JavaScript can create animations, but they have different performance characteristics, capabilities, and use cases. Understanding when to use each is crucial for smooth, performant animations.

Comparison Overview​

CSS Animations​

/* CSS Transitions - state changes */
.button {
background: blue;
transform: scale(1);
transition: transform 0.3s ease, background 0.3s ease;
}

.button:hover {
background: darkblue;
transform: scale(1.05);
}

/* CSS Keyframe Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.fade-in {
animation: fadeIn 0.5s ease forwards;
}

/* Complex keyframe animation */
@keyframes pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}

.pulsing {
animation: pulse 2s ease-in-out infinite;
}

JavaScript Animations​

// requestAnimationFrame - most control
function animate(element, startX, endX, duration) {
const startTime = performance.now();

function frame(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);

// Easing function
const easeOut = 1 - Math.pow(1 - progress, 3);

const currentX = startX + (endX - startX) * easeOut;
element.style.transform = `translateX(${currentX}px)`;

if (progress < 1) {
requestAnimationFrame(frame);
}
}

requestAnimationFrame(frame);
}

// Web Animations API (WAAPI)
element.animate(
[
{ transform: "translateX(0)", opacity: 1 },
{ transform: "translateX(100px)", opacity: 0 },
],
{
duration: 500,
easing: "ease-out",
fill: "forwards",
}
);

// WAAPI with control
const animation = element.animate(keyframes, options);
animation.pause();
animation.play();
animation.reverse();
animation.playbackRate = 0.5; // Half speed
animation.finished.then(() => console.log("Done!"));

Performance Comparison​

Properties for Smooth Animation​

/* ✅ GPU-accelerated (compositor thread) */
.smooth-animation {
/* Only these properties are truly GPU-accelerated */
transform: translateX(100px);
opacity: 0.5;
}

/* ❌ Triggers layout/paint (main thread) */
.janky-animation {
/* Avoid animating these */
width: 200px;
height: 100px;
top: 50px;
left: 50px;
margin: 20px;
padding: 10px;
border-width: 2px;
font-size: 16px;
}

/* ✅ Use transform instead of position */
.move-element {
/* Instead of: left: 100px */
transform: translateX(100px);

/* Instead of: width/height changes */
transform: scale(1.5);
}

Use Cases​

Animation TypeRecommended Approach
Hover effectsCSS transitions
Loading spinnersCSS keyframes
Page transitionsCSS or WAAPI
Scroll-linkedJavaScript (Intersection Observer + WAAPI)
Complex sequencesJavaScript (WAAPI or GSAP)
Physics-basedJavaScript
Interactive/gameJavaScript
User gesture drivenJavaScript

Animation Libraries​

// GSAP (GreenSock) - powerful animation library
gsap.to(".box", {
x: 100,
rotation: 360,
duration: 1,
ease: "power2.out",
});

// Timeline for sequences
const tl = gsap.timeline();
tl.to(".header", { y: -100, duration: 0.5 })
.to(".content", { opacity: 1, duration: 0.3 })
.to(".footer", { y: 0, duration: 0.5 });

// Framer Motion (React)
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
Content
</motion.div>;

Scroll-Linked Animations​

// Modern CSS scroll-driven animations
@keyframes reveal {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}

.scroll-reveal {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}

// JavaScript approach with Intersection Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.animate([
{ opacity: 0, transform: 'translateY(50px)' },
{ opacity: 1, transform: 'translateY(0)' }
], {
duration: 500,
fill: 'forwards'
});
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1 });

document.querySelectorAll('.animate-on-scroll')
.forEach(el => observer.observe(el));

Best Practices​

/* 1. Use will-change sparingly */
.will-animate {
will-change: transform, opacity;
}

/* 2. Prefer transform and opacity */
.performant {
transform: translateX(100px) scale(1.1);
opacity: 0.8;
}

/* 3. Use contain for complex lists */
.list-item {
contain: layout style paint;
}

/* 4. Reduce motion for accessibility */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
// Check for reduced motion preference
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;

if (!prefersReducedMotion) {
// Run animations
}

Decision Flowchart​

Key Points​

  • CSS: Simple, declarative, auto-optimized
  • JavaScript: Complex, interactive, more control
  • Animate transform and opacity for performance
  • Web Animations API bridges CSS and JS
  • Use prefers-reduced-motion for accessibility
  • Libraries like GSAP for complex animations
  • Measure with DevTools Performance tab