JavaScriptBrowser APIs

requestAnimationFrame()

Schedules a function to run right before the next screen repaint. Used for smooth animations driven by JavaScript.

Returns
A number, the identifier of the scheduled call.
On this page

Example

#
JavaScript
const bar = document.querySelector('#bar')
const duration = 600
let start

function step(timestamp) {
  if (start === undefined) start = timestamp
  const progress = Math.min((timestamp - start) / duration, 1)
  bar.style.width = `${progress * 100}%`

  if (progress < 1) {
    requestAnimationFrame(step)
  } else {
    console.log('Bar filled:', bar.style.width)
  }
}

requestAnimationFrame(step)
console.log('Animation scheduled')
Console
Animation scheduled
Bar filled: 100%

Progress is computed from elapsed time, so the bar fills in 0.6 s no matter the frame rate. Click “Run again” to see the animation once more.

Definition and usage

#

The requestAnimationFrame() function asks the browser to call the given function right before it draws the next frame. That usually happens 60 times a second, and more often on higher-refresh-rate screens. Visual changes then land on screen exactly when the browser is already redrawing the page anyway, so the animation looks smoother than with setInterval().

The callback runs only once. To build an animation, schedule the next call by calling requestAnimationFrame() again at the end of each frame. The callback receives a timestamp in milliseconds (on the same scale as performance.now()), which lets you compute progress from elapsed time rather than frame count. That way the animation takes the same amount of time on every screen.

The call returns an identifier that, passed to cancelAnimationFrame(), cancels the scheduled frame. In background tabs and hidden frames, the browser pauses calls to save battery. Simple transitions are better described in CSS (transition, @keyframes), and requestAnimationFrame() is best left for motion that depends on calculations, such as in games and charts.

Syntax

#
Syntax
requestAnimationFrame(callback)

cancelAnimationFrame(requestId)

Parameters

#
  • callback

    function (timestamp)

    The function called before the next frame. It receives a timestamp in milliseconds.
  • requestId

    number

    The identifier returned by requestAnimationFrame(), passed to cancelAnimationFrame().

More examples

#
Points counter, step by step
JavaScript
const points = document.querySelector('#points')
const target = 150
let value = 0

function tick() {
  value = Math.min(value + 30, target)
  points.textContent = value
  console.log(value)
  if (value < target) requestAnimationFrame(tick)
}

requestAnimationFrame(tick)
Console
30
60
90
120
150

Each frame increases the counter by 30, so the animation finishes after five frames, around 80 ms on a 60 Hz screen. On a 120 Hz screen the same code runs twice as fast, which is why longer animations should compute their step from elapsed time instead, as in the first example.

Cancelling a scheduled frame
JavaScript
const tile = document.querySelector('#tile')

const moveId = requestAnimationFrame(() => {
  tile.style.transform = 'translateX(200px)'
  console.log('Moved the tile')
})

requestAnimationFrame(() => {
  console.log('Frame ready, offset:', tile.style.transform || 'none')
})

cancelAnimationFrame(moveId)
console.log('Frame id is', typeof moveId)
Console
Frame id is number
Frame ready, offset: none

The first scheduled function was cancelled before the next frame, so the tile didn’t move. The second one ran normally.

At most one update per frame
JavaScript
const header = document.querySelector('header')
let scheduled = false

window.addEventListener('scroll', () => {
  if (scheduled) return
  scheduled = true

  requestAnimationFrame(() => {
    header.classList.toggle('with-shadow', window.scrollY > 0)
    scheduled = false
  })
})

The scroll event can fire several times within a single frame. The scheduled flag makes sure the header’s appearance updates at most once per frame, right before painting.

Browser support

#

Widely available

Works in all modern browsers, phones included. You can use it without worry.

  • Chrome
  • Edge
  • Firefox
  • Safari

Good practices

#
  • Compute animation progress from elapsed time (timestamp), not from frame count. A 120 Hz screen has twice as many frames as a 60 Hz one.
  • Each call schedules only one frame. If the animation should keep going, call requestAnimationFrame() again inside the function.
  • Describe simple transitions in CSS with transition or @keyframes. The browser animates them more efficiently and without JavaScript code.

Related entries

#

Spotted a mistake or missing an example? Write to us.