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