requestAnimationFrame vs setTimeout — The Secret to Smooth Rendering

When building web animations or scroll effects, you’ve probably heard people say: “Don’t use setTimeout, use requestAnimationFrame instead.”
At first, I thought — aren’t they just both timers? But after testing, the difference was way more significant than I expected.
Let’s break it down in a practical way, with real examples and performance reasoning behind it.
💡 Basic Concept
| Category | setTimeout | requestAnimationFrame |
|---|---|---|
| Execution Interval | Runs after a fixed delay (ms) | Runs once per browser repaint cycle |
| Timing | May drift or delay depending on load | Aligned with screen refresh rate (≈16.6ms @ 60Hz) |
| Purpose | General-purpose timer | Optimized for rendering and animation |
| CPU Efficiency | Low (runs even when tab is inactive) | High (automatically pauses in background) |
So, requestAnimationFrame isn’t just a faster timer —
it’s a smarter timing mechanism that syncs with the browser’s rendering cycle.
🧪 A Simple Experiment
You can feel the difference instantly.
Create a new HTML file and test both approaches side by side:
<div id="box" style="width:50px;height:50px;background:skyblue;position:absolute;"></div>
<script>
const box = document.getElementById('box')
let pos = 0
// Using setTimeout
function moveWithTimeout() {
pos += 2
box.style.left = pos + 'px'
if (pos < 500) setTimeout(moveWithTimeout, 16)
}
// Using requestAnimationFrame
function moveWithRAF() {
pos += 2
box.style.left = pos + 'px'
if (pos < 500) requestAnimationFrame(moveWithRAF)
}
const btn1 = document.createElement('button')
btn1.textContent = 'Run with setTimeout'
btn1.onclick = moveWithTimeout
const btn2 = document.createElement('button')
btn2.textContent = 'Run with requestAnimationFrame'
btn2.onclick = moveWithRAF
document.body.append(btn1, btn2)
</script>Run both, and you’ll notice the difference immediately:
| Behavior | setTimeout | requestAnimationFrame |
|---|---|---|
| Visual Smoothness | Slightly choppy | Very smooth |
| CPU Load | Higher | Lower |
| When Tab Inactive | Keeps running | Pauses automatically |
| Syncs to Display Refresh | ❌ | ✅ |
⚙️ Why Does This Happen?
Browsers have an internal render loop, usually updating around 60 times per second (≈16.6ms).
requestAnimationFrame hooks directly into that loop — it executes just before the next paint.
Meanwhile, setTimeout doesn’t care about frames.
It runs independently, which often means it fires in the middle of a frame, causing Reflow/Repaint mismatches.
Think of it like this:
setTimeoutmoves without caring about the rhythm,
whilerequestAnimationFramedances perfectly to the browser’s beat.
🧠 Practical Differences in Real Projects
1️⃣ Scroll or Drag Animations
// ❌ Using setTimeout
window.addEventListener('scroll', () => {
setTimeout(() => {
console.log(window.scrollY)
}, 16)
})
// ✅ Using requestAnimationFrame
let ticking = false
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
console.log(window.scrollY)
ticking = false
})
ticking = true
}
})Because requestAnimationFrame runs right before a repaint,
it handles frequent scroll events much more efficiently and smoothly.
2️⃣ React UI Animation
When React components update too frequently, it can cause visible lag.
Using requestAnimationFrame helps spread updates in sync with the frame rate.
function SmoothCounter() {
const [count, setCount] = useState(0)
const handleIncrease = () => {
let current = count
const animate = () => {
current += 1
setCount(current)
if (current < 100) requestAnimationFrame(animate)
}
requestAnimationFrame(animate)
}
return <button onClick={handleIncrease}>Count: {count}</button>
}This approach makes UI updates look smooth and natural.
The same logic using setTimeout often produces uneven or stuttering motion.
3️⃣ Canvas, Charts, or Game Loops
In graphics-heavy cases like Canvas drawing or WebGL scenes, requestAnimationFrame is essential.
function render() {
drawScene()
requestAnimationFrame(render)
}
render()It syncs perfectly with the GPU and helps keep FPS stable and consistent.
🔍 Measuring Performance
You can verify the difference in Chrome DevTools → Performance tab.
setTimeout: irregular frame gaps, visible jank, and higher CPU usagerequestAnimationFrame: consistent frame spacing and smoother painting
In terms of frames per second (FPS),
setTimeout often fluctuates between 40–50 FPS, while requestAnimationFrame holds steady around 60 FPS.
⚡ Summary
| Comparison | setTimeout | requestAnimationFrame |
|---|---|---|
| Timing | Fixed delay | Synced with render cycle |
| Smoothness | Moderate | Excellent |
| CPU Usage | Higher | Lower |
| Background Tabs | Keeps running | Auto-paused |
| Best Use Case | Simple timers | Animations, scrolling, charts |
💬 Final Thoughts
They may look similar, but their purposes are completely different.
setTimeout is for general timing, while requestAnimationFrame is designed for frame-perfect rendering.
Whenever you’re updating something visible — like scroll indicators, progress bars, or UI animations —
go with requestAnimationFrame.
In the end, smooth UI is all about timing.
Move in sync with the browser’s rhythm, and your app will feel faster, even if it’s not technically doing less work. 😌
#requestAnimationFrame #setTimeout #JavaScript #WebPerformance #Animation #BrowserRendering #ReactPerformance #FPS #FrontendOptimization