How to use requestAnimationFrame in JavaScript
Creating smooth animations in JavaScript requires synchronizing with the browser’s refresh rate for optimal performance.
As the creator of CoreUI with over 25 years of JavaScript experience since 2000, I’ve built countless animated UI components and interactive visualizations using proper animation techniques.
The standard approach uses requestAnimationFrame() which schedules animation updates to match the display refresh rate, typically 60 frames per second.
This provides smoother animations than setInterval or setTimeout while being more battery-efficient.
Use requestAnimationFrame() to create animations synchronized with the browser’s repaint cycle.
function animate() {
// Update animation state
position += speed
// Render the change
element.style.left = position + 'px'
// Schedule next frame
requestAnimationFrame(animate)
}
// Start animation
requestAnimationFrame(animate)
The requestAnimationFrame() calls the animation function before the next repaint. The function updates state and renders changes. Calling requestAnimationFrame() again schedules the next frame. This creates a loop synchronized with screen refresh, ensuring smooth 60fps animations.
Creating a Simple Moving Animation
Animate an element across the screen with smooth motion.
const element = document.getElementById('box')
let position = 0
let animationId
function move() {
position += 2
if (position > 500) {
cancelAnimationFrame(animationId)
return
}
element.style.transform = `translateX(${position}px)`
animationId = requestAnimationFrame(move)
}
animationId = requestAnimationFrame(move)
The animation updates position by 2 pixels per frame. The condition stops the animation at 500 pixels. The cancelAnimationFrame() stops the animation loop. The animationId allows canceling the animation later. Using transform instead of left provides better performance.
Using Delta Time for Consistent Animation Speed
Make animations frame-rate independent using elapsed time.
const element = document.getElementById('box')
let position = 0
let lastTime = 0
const speed = 0.2 // pixels per millisecond
function animate(currentTime) {
const deltaTime = currentTime - lastTime
lastTime = currentTime
position += speed * deltaTime
element.style.transform = `translateX(${position}px)`
requestAnimationFrame(animate)
}
requestAnimationFrame(animate)
The currentTime parameter is a DOMHighResTimeStamp in milliseconds. The deltaTime calculates elapsed time since the last frame. Multiplying speed by delta time ensures consistent motion regardless of frame rate. This prevents animations from running faster on high-refresh displays or slower during lag.
Creating Easing Animations
Apply easing functions for natural-looking motion.
const element = document.getElementById('box')
const startPos = 0
const endPos = 500
const duration = 2000 // 2 seconds
let startTime = null
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
}
function animate(currentTime) {
if (!startTime) startTime = currentTime
const elapsed = currentTime - startTime
const progress = Math.min(elapsed / duration, 1)
const easedProgress = easeInOutQuad(progress)
const position = startPos + (endPos - startPos) * easedProgress
element.style.transform = `translateX(${position}px)`
if (progress < 1) {
requestAnimationFrame(animate)
}
}
requestAnimationFrame(animate)
The progress value goes from 0 to 1 over the duration. The easing function transforms linear progress into curved motion. The easedProgress creates smooth acceleration and deceleration. The animation stops when progress reaches 1. This produces professional-looking animations.
Animating Multiple Properties
Simultaneously animate multiple CSS properties.
const element = document.getElementById('box')
let rotation = 0
let scale = 1
let opacity = 1
function animate() {
rotation += 2
scale = 1 + Math.sin(rotation * 0.02) * 0.5
opacity = 0.5 + Math.sin(rotation * 0.03) * 0.5
element.style.transform = `rotate(${rotation}deg) scale(${scale})`
element.style.opacity = opacity
requestAnimationFrame(animate)
}
requestAnimationFrame(animate)
Each property updates independently. The Math.sin() creates oscillating values for smooth back-and-forth motion. The transform property combines multiple transformations efficiently. Animating transform and opacity is GPU-accelerated for best performance.
Creating a Performance-Optimized Game Loop
Build a game loop that separates update logic from rendering.
const canvas = document.getElementById('gameCanvas')
const ctx = canvas.getContext('2d')
let lastTime = 0
const fixedDeltaTime = 1000 / 60 // 60 FPS
let accumulator = 0
const gameState = {
x: 0,
y: 0,
velocityX: 2,
velocityY: 1
}
function update(deltaTime) {
gameState.x += gameState.velocityX
gameState.y += gameState.velocityY
if (gameState.x > canvas.width || gameState.x < 0) {
gameState.velocityX *= -1
}
if (gameState.y > canvas.height || gameState.y < 0) {
gameState.velocityY *= -1
}
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = 'blue'
ctx.fillRect(gameState.x, gameState.y, 50, 50)
}
function gameLoop(currentTime) {
const deltaTime = currentTime - lastTime
lastTime = currentTime
accumulator += deltaTime
while (accumulator >= fixedDeltaTime) {
update(fixedDeltaTime)
accumulator -= fixedDeltaTime
}
render()
requestAnimationFrame(gameLoop)
}
requestAnimationFrame(gameLoop)
The fixed time step ensures physics updates run at consistent intervals. The accumulator handles variable frame rates. The update loop runs multiple times if frames are slow. The render function draws the current state. This pattern is standard in game development for deterministic physics.
Pausing and Resuming Animations
Control animation playback with start, stop, and pause.
const element = document.getElementById('box')
let position = 0
let animationId = null
let isRunning = false
function animate() {
position += 2
element.style.transform = `translateX(${position}px)`
if (isRunning) {
animationId = requestAnimationFrame(animate)
}
}
function start() {
if (!isRunning) {
isRunning = true
animationId = requestAnimationFrame(animate)
}
}
function pause() {
isRunning = false
if (animationId) {
cancelAnimationFrame(animationId)
}
}
function reset() {
pause()
position = 0
element.style.transform = 'translateX(0px)'
}
// Control buttons
document.getElementById('startBtn').addEventListener('click', start)
document.getElementById('pauseBtn').addEventListener('click', pause)
document.getElementById('resetBtn').addEventListener('click', reset)
The isRunning flag controls whether the animation continues. The start() function begins or resumes animation. The pause() function stops it without resetting state. The reset() function returns to initial state. This provides full animation control for interactive applications.
Best Practice Note
This is the same animation technique we use in CoreUI components for smooth transitions, loading indicators, and interactive charts. Always prefer requestAnimationFrame over setInterval or setTimeout for visual updates - it’s automatically paused when the tab is inactive, saving battery and CPU. Use transform and opacity for animations as they’re GPU-accelerated and don’t trigger layout recalculation. For complex animations, consider using the Web Animations API or CSS animations which are even more performant. Monitor performance using Chrome DevTools Performance tab to ensure 60fps. For data visualizations that require frequent updates, consider debouncing or throttling animation updates to maintain performance with large datasets.



