Problem Statement
Why use requestAnimationFrame instead of setInterval for animations?
Explanation
RequestAnimationFrame synchronizes with the browser's refresh rate, typically 60 FPS.
It automatically pauses when the tab is inactive, saving CPU and battery.
SetInterval runs at fixed intervals regardless of browser rendering, causing jank.
RAF ensures animations run smoothly by timing updates with screen repaints.
The browser can optimize RAF calls, batching them together.
Understanding RAF is essential for creating smooth, performant animations.
Code Solution
SolutionRead Only
// BAD: Using setInterval
let position = 0;
setInterval(() => {
position += 2;
element.style.left = position + 'px';
}, 16); // ~60 FPS
// Problems:
// - Not synced with screen refresh
// - Runs even when tab inactive
// - Can cause frame drops
// GOOD: Using requestAnimationFrame
let position2 = 0;
function animate() {
position2 += 2;
element.style.left = position2 + 'px';
if (position2 < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
// Benefits:
// - Synced with refresh rate
// - Pauses when tab inactive
// - Smooth animations
// ANIMATION WITH TIMESTAMP
function animateWithTime(timestamp) {
// timestamp is high-resolution time
const progress = timestamp / 1000; // Convert to seconds
element.style.left = (progress * 100) % 500 + 'px';
requestAnimationFrame(animateWithTime);
}
requestAnimationFrame(animateWithTime);
// SMOOTH MOVEMENT
let lastTime = 0;
let x = 0;
const speed = 100; // pixels per second
function smoothMove(currentTime) {
// Calculate delta time
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
// Update position based on time
x += speed * deltaTime;
element.style.left = x + 'px';
if (x < 500) {
requestAnimationFrame(smoothMove);
}
}
requestAnimationFrame(smoothMove);
// CANCELING ANIMATION
let animationId;
function animate() {
// Animation code
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
// Stop animation
cancelAnimationFrame(animationId);
// GAME LOOP PATTERN
class Game {
constructor() {
this.running = false;
this.lastTime = 0;
}
start() {
this.running = true;
requestAnimationFrame((time) => this.gameLoop(time));
}
stop() {
this.running = false;
}
gameLoop(currentTime) {
if (!this.running) return;
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;
// Update game state
this.update(deltaTime);
// Render
this.render();
// Continue loop
requestAnimationFrame((time) => this.gameLoop(time));
}
update(deltaTime) {
// Update game objects
}
render() {
// Draw to canvas
}
}
const game = new Game();
game.start();
// SCROLLING PARALLAX
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
updateParallax();
ticking = false;
});
ticking = true;
}
});
function updateParallax() {
const scrollY = window.scrollY;
background.style.transform = `translateY(${scrollY * 0.5}px)`;
}
// SMOOTH COUNTER
function animateValue(start, end, duration) {
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function
const easeProgress = progress * (2 - progress);
const current = start + (end - start) * easeProgress;
element.textContent = Math.floor(current);
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
animateValue(0, 1000, 2000); // Count from 0 to 1000 in 2 seconds
// FPS COUNTER
let fps = 0;
let frameCount = 0;
let lastFpsUpdate = 0;
function countFPS(currentTime) {
frameCount++;
if (currentTime - lastFpsUpdate >= 1000) {
fps = frameCount;
frameCount = 0;
lastFpsUpdate = currentTime;
console.log('FPS:', fps);
}
requestAnimationFrame(countFPS);
}
requestAnimationFrame(countFPS);
// BATCH DOM UPDATES
const updates = [];
let scheduled = false;
function scheduleUpdate(callback) {
updates.push(callback);
if (!scheduled) {
scheduled = true;
requestAnimationFrame(processBatch);
}
}
function processBatch() {
const batch = updates.slice();
updates.length = 0;
scheduled = false;
batch.forEach(callback => callback());
}
// Usage
scheduleUpdate(() => {
element1.style.left = '100px';
});
scheduleUpdate(() => {
element2.style.top = '200px';
});
// Both updates happen in same frame!