completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Animation with JavaScript

Make browser motion feel smooth with requestAnimationFrame, elapsed-time loops, cubic-bezier easing, CSS animation choices, and the Web Animations API.

By the end you can
  • 01
    Run a frame loopUse requestAnimationFrame and its timestamp to move something by the real time that passed, and stop the loop with cancelAnimationFrame.
  • 02
    Shape the speedExplain what a timing function does, what the four cubic-bezier() numbers mean, and why motion can go past the end.
  • 03
    Pick the right toolChoose a CSS transition, CSS keyframes, element.animate(), or a JavaScript frame loop for a real page task.

Smooth motion, one frame at a time

An animation is a picture that changes a little at a time, many times a second. On a web page, JavaScript can slide a box, fade in a panel, or move a game character.

The browser draws the page on the screen again and again. Each new drawing is called a repaint, and one drawing is called a frame. Good animation code does three things:

  • It changes the picture just before the browser draws the next frame.
  • It measures how much time really passed, instead of guessing.
  • It moves less for people who ask their device for less motion.

This lesson starts with requestAnimationFrame, the tool for the first two. Then it covers timing functions, how to choose between CSS and JavaScript, and element.animate().

In the Timers lesson you met setInterval. It runs your code every few milliseconds, on its own clock. It does not know when the browser is about to draw. For motion, you want to ask two other questions:

  • Is the browser ready to draw the next frame?
  • How much time has passed since the last frame?
Real-life analogyDrawing a flipbook

Think of drawing a flipbook. You draw one page at a time, and you start the next page only when you are ready for it. A kitchen timer is different: it rings when its time is up, whether you are ready or not.

In real life: You are ready to draw the next page
In JavaScript: requestAnimationFrame(frame) runs frame just before the next repaint
In real life: You check the clock before each page
In JavaScript: The timestamp tells you how much time has passed
In real life: A kitchen timer rings on its own
In JavaScript: setInterval runs on its own clock, not when the screen is drawn
The main idea

JavaScript animation is code that changes what you see, a little at a time. Good animation code makes one change per frame, just before the browser repaints. It uses the real time that has passed, because frames do not all take the same time.

requestAnimationFrame

INTERACTIVE

requestAnimationFrame(callback) asks the browser: "Run this function once, just before the next repaint." A callback is a function you hand over so that someone else can call it later. People often shorten requestAnimationFrame to rAF.

The browser passes one number to your callback: a timestamp. It is the time of this frame, in milliseconds (thousandths of a second). Every rAF callback in the same frame gets the same timestamp, so two animations on one page stay in step.

How often does a frame come? That depends on the screen. Many screens draw 60 frames a second. Others draw 90, 120, or a number that changes. In a hidden browser tab, frames may slow down or stop.

A loop that moves a dot, one frame at a timePop out in the code editor (opens in a new tab)JavaScript
let rafId;let previous;let position = 0;const speed = 0.12; // pixels per millisecond function frame(timestamp) {  const elapsed = previous === undefined ? 0 : timestamp - previous;  previous = timestamp;  position += speed * elapsed;  dot.style.transform = `translateX(${position}px)`;  rafId = requestAnimationFrame(frame);} rafId = requestAnimationFrame(frame);// Later: cancelAnimationFrame(rafId);

Line by line:

  1. Lines 1 to 4 make four variables. rafId keeps the request number. previous keeps the last timestamp. position is where the dot is. speed is 0.12 pixels per millisecond.
  2. Line 6 starts the frame function. The browser calls it with the timestamp.
  3. Line 7 works out elapsed: the time since the last frame. On the very first frame there is no last frame, so it uses 0.
  4. Line 8 saves this timestamp for the next frame.
  5. Line 9 adds speed times elapsed time to the position.
  6. Line 10 moves the dot on the screen with a CSS transform.
  7. Line 11 asks for the next frame. This is what makes it a loop.
  8. Line 14 asks for the very first frame. Line 15 is a note: later, cancelAnimationFrame(rafId) stops the loop.
A loop. The browser calls frame(timestamp) just before it paints the screen. frame moves the dot, the screen shows the dot, and requestAnimationFrame(frame) asks for the next frame.frame(timestamp)runs before a paintscreendotthe dot movesrequestAnimationFrame(frame)
The browser calls frame(timestamp) just before it paints the screen. Line 10 moves the dot, and line 11 asks for the next frame.

The most important line is line 7, the elapsed time. You could move the dot a fixed amount every frame, like 2 pixels. But then the speed depends on the screen: a screen with 120 frames a second moves the dot twice as fast as one with 60. With elapsed time, the speed is set in pixels per millisecond. If a frame comes late, the dot moves farther in that frame, so it stays on time.

Work out how full a progress bar isPop out in the code editor (opens in a new tab)JavaScript
const startedAt = 1000;
const now = 1300;
const duration = 1000;
const progress = progressFraction(startedAt, now, duration);
bar.style.transform = `scaleX(${progress})`;
console.log(`${Math.round(progress * 100)}%`);

This example uses fixed times, so it prints the same thing every time. progressFraction is a small helper this lesson provides. It works out (now − start) ÷ duration and keeps the answer between 0 and 1.

  1. Lines 1 to 3: the task started at 1,000 ms, it is now 1,300 ms, and the task takes 1,000 ms.
  2. Line 4: 300 ms out of 1,000 ms have passed, so progress is 0.3.
  3. Line 5 stretches the bar to 30% of its full width with scaleX(0.3).
  4. Line 6 prints 30%.

On a real page, a rAF loop would give you a new "now" on every frame, and the bar would grow as time passes.

Frame loop lab: move a dot with rAF
A requestAnimationFrame loopPop out in the code editor (opens in a new tab)JavaScript
let rafId;let previous;let position = 0;const speed = 0.12; // pixels per millisecond function frame(timestamp) {  const elapsed = previous === undefined ? 0 : timestamp - previous;  previous = timestamp;  position += speed * elapsed;  dot.style.transform = `translateX(${position}px)`;  rafId = requestAnimationFrame(frame);} rafId = requestAnimationFrame(frame);// Later: cancelAnimationFrame(rafId);
Tracknot measured
Mode
by elapsed time
Position
0.0 px
Slow frame
not yet
Try it yourself

Press Start when you're ready. Nothing moves until you do. Reset uses cancelAnimationFrame with the saved request id.

The number of frames a second follows your screen, so it may show 60, 90, 120, or another number. Most browsers slow down or stop rAF in hidden tabs.

The lab does not start moving when the page loads. Real pages should do the same, unless the motion is needed and safe. Let the reader press a button to start.

requestAnimationFrame returns a number called the request id. Always keep it. To stop the loop, pass it to cancelAnimationFrame(id). Do this when the reader pauses or resets, and when that part of the page is removed.

Pause the loop when the tab is hiddenPop out in the code editor (opens in a new tab)JavaScript
function pauseWhenHidden() {
  if (document.hidden && rafId !== undefined) {
    cancelAnimationFrame(rafId);
    rafId = undefined;
    previous = undefined;
  } else if (!document.hidden && rafId === undefined) {
    rafId = requestAnimationFrame(frame);
  }
}

document.addEventListener("visibilitychange", pauseWhenHidden);

Line by line:

  1. Line 11 runs pauseWhenHidden each time the tab is hidden or shown again. That is the visibilitychange event.
  2. Lines 2 and 3: if the tab is hidden and a frame is waiting, cancel that frame.
  3. Lines 4 and 5 clear the request id and the last timestamp.
  4. Lines 6 and 7: if the tab is shown again and no frame is waiting, ask for a new one.

Why clear previous on line 5? If you don't, the first frame after the tab comes back counts the whole hidden time as elapsed time, and the dot jumps far ahead. Browsers often slow down or stop rAF in hidden tabs anyway, but your own variables still need to be right. When that part of the page is removed, remove the visibilitychange listener too.

The replay below compares the two ways to move the dot. It uses three made-up timestamps: 0, 16.7, and 50 ms. The last frame is late: it comes 33.3 ms after the one before, not about 16.7 ms.

Left: position += 2 prints 2.00 at 16.7 ms and 4.00 at 50 ms, so it falls behind. Right: position += 0.12 * elapsed prints 2.00 at 16.7 ms and 6.00 at 50 ms, so it keeps up.position += 22.004.00at 16.7 msat 50 msfalls behindposition += 0.12 * elapsed2.006.00at 16.7 msat 50 mskeeps up
Both use the same frames: one at 16.7 ms, then a late one at 50 ms. Adding 2 each time ends at 4.00, but using the elapsed time catches up to 6.00.
Replay: the same timestamps, two ways to move
Step 0 of 9Ready
Your turn: follow the blue line

Switch the setting and replay. With the same timestamps, moving by elapsed time keeps up after the late frame, and moving a fixed amount falls behind.

Running in
  1. script
Next: line 1
let position = 0;let previous = timestamps[0];for (const timestamp of timestamps.slice(1)) {  const elapsed = timestamp - previous;  position += 0.12 * elapsed;  console.log(position.toFixed(2));  previous = timestamp;}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
How the dot moves
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Press Step to go line by line. Then switch the setting and replay. Moving by elapsed time prints 2.00 and 6.00. Moving a fixed 2 pixels per frame prints 2.00 and 4.00.

Timing functions and Bézier curves

PLAYGROUND

Moving at one speed the whole time often looks stiff. A drawer that starts slowly and stops slowly looks natural. A shake that warns about an error should start fast.

A timing function, also called an easing, controls this. It takes how much of the time has passed, from 0 to 1. It gives back how far along the motion is, usually also from 0 to 1.

Real-life analogyPulling a drawer

Think about pulling open a drawer. You can start gently, slow down near the end, or pull at one steady speed. Each way feels different, even though the drawer goes the same distance in the same time.

In real life: The drawer starts slowly, then speeds up
In JavaScript: ease-in starts slowly
In real life: The drawer slows down near the end
In JavaScript: ease-out ends slowly
In real life: The drawer moves at one steady speed
In JavaScript: linear keeps the same speed
In real life: You choose exactly how it slides
In JavaScript: cubic-bezier() sets your own curve

CSS has names for common timing functions: linear, ease, ease-in, ease-out, and ease-in-out. Each one is a cubic Bézier curve: a smooth curve set by four numbers, written cubic-bezier(x1, y1, x2, y2). The x numbers are about time. The y numbers are about progress.

The time 0.5 goes into easing(0.5), the ease curve, and comes out as 0.802. Half the time has passed, and the motion is about 80 percent done.time0.5half the timeeasing(0.5)the ease curve0.80280% of the way
A timing function takes how much of the time has passed and gives how far along the motion is. With the ease curve, half the time gives 0.802.

The lab below shows this code. cubicBezier is a helper this lesson provides. Line 1 makes an easing function from the four numbers of ease. Lines 2 to 4 print 0, 0.802, and 1. So with ease, half the time gives about 80% of the motion: it moves fast at first and slows near the end.

Bézier lab: bend the speed curve
A small cubic-bezier helperPop out in the code editor (opens in a new tab)JavaScript
const easing = cubicBezier(0.25, 0.1, 0.25, 1);console.log(easing(0));console.log(easing(0.5).toFixed(3));console.log(easing(1));
cubic-bezier(0.25, 0.1, 0.25, 1)copy this easing
Try it yourself

The x sliders only go from 0 to 1, because time must move forward. The y sliders can go below 0 or above 1. Then the motion goes past the end and comes back, like a bounce.

Move the sliders, then copy the cubic-bezier(...) text. You can use it in a CSS transition, in CSS keyframes, or in the timing of element.animate().

Two rules for the four numbers:

  • The x numbers must stay from 0 to 1. They are about time, and time can't go backward or past the end.
  • The y numbers can go below 0 or above 1. Then the motion goes past the end and comes back, like a bounce. This is called overshoot.
A newer easing: linear()

Newer browsers also have a CSS linear() timing function. It builds a curve from several straight pieces. Older browsers don't know it. If your page needs it, check first with CSS.supports() in the browser, after the page has loaded, and keep a simpler easing as a backup.

CSS or JavaScript animation?

SORT

CSS can animate too, and it is often simpler and smoother. Use it when you know the motion ahead of time. CSS has two tools for this:

  • A transition animates a change from one style to another, like a button that grows a little when the mouse is over it.
  • A @keyframes animation plays a list of steps you wrote ahead of time, like a spinner that turns again and again.

With CSS, the browser works out every in-between step for you. For transform and opacity, it can often do this on the compositor: a separate part of the browser that moves and fades parts of the page that are already drawn. It does not need your JavaScript for that.

Your JavaScript runs on the main thread: the one line of work where the browser also handles clicks and works out the page layout. Use JavaScript for motion that needs decisions while it runs:

  • live measurements, like how fast the page is scrolling
  • physics, like a ball that bounces
  • drawing on a canvas
  • keyframes that your code builds
  • buttons to pause, reverse, or finish the motion
Real-life analogyA cartoon or a live artist

A cartoon on TV plays scenes that were drawn ahead of time. A live artist draws each picture as they go, because the next picture depends on what just happened.

In real life: A cartoon plays scenes drawn ahead of time
In JavaScript: CSS keyframes: the browser plays them
In real life: A live artist draws the next picture now
In JavaScript: JavaScript can decide each frame
In real life: The scenes are known before the show
In JavaScript: CSS suits motion you know ahead of time
In real life: Each new picture needs a new choice
In JavaScript: JavaScript gives control, but uses the main thread

The table compares the four tools. Read each row as: "use this tool for these jobs; this is what it does well; this is what to watch for."

Which animation tool to use
ToolUse it forWhat it does wellWhat to watch for
CSS transitionA change from one style to another, like a hover effect or a panel that opens and closes.The browser can often animate transform and opacity very smoothly.Your code can't make a new choice on each frame.
CSS @keyframesMotion you can reuse and know ahead of time, like a loading spinner.You write the steps once, and the browser plays them.It can't run logic or live physics.
element.animate()Keyframes your code builds, with buttons to control the motion.It returns an Animation object with play(), pause(), reverse(), and finish().You still need to clean up, and to think about fill.
requestAnimationFrameCanvas loops, games, physics, and anything that measures on every frame.Your callback gets the frame's timestamp, in step with the screen.You run the loop yourself, and it uses main-thread time.

CSS can also tell your JavaScript when an animation ends:

  • transitionend fires when the transition of one property ends.
  • animationend fires when a CSS @keyframes animation ends.
  • If a transition is stopped early, transitionend does not fire.
Run code when a CSS animation endsPop out in the code editor (opens in a new tab)JavaScript
const button = document.querySelector("button");
const spinner = document.querySelector(".spinner");

button.addEventListener("transitionend", (event) => {
  console.log(event.propertyName);
});
spinner.addEventListener("animationend", () => {
  spinner.classList.remove("is-spinning");
});

Line by line:

  1. Lines 1 and 2 find the button and the spinner on the page.
  2. Lines 4 to 6: when one of the button's transitions ends, print the name of that property, like transform.
  3. Lines 7 to 9: when the spinner's animation ends, remove its is-spinning class.

When that part of the page is removed, remove these listeners too.

A CSS spinner you can stopPop out in the code editor (opens in a new tab)HTML
<button type="button" id="stop-spinner">Stop loading</button>
<span class="spinner is-spinning" role="status" aria-label="Loading"></span>

<script>
document.querySelector("#stop-spinner").addEventListener("click", () => {
  document.querySelector(".spinner").classList.remove("is-spinning");
});
</script>

<style>
.spinner { display: inline-block; inline-size: 1rem; block-size: 1rem; border: 2px solid currentColor; border-inline-end-color: transparent; border-radius: 50%; }
.spinner.is-spinning { animation: spin 700ms linear infinite; }
@keyframes spin { to { transform: rotate(1turn); } }
@media (prefers-reduced-motion: reduce) { .spinner.is-spinning { animation-duration: 1ms; animation-iteration-count: 1; } }
</style>

Here CSS does the turning. prefers-reduced-motion: reduce on line 14 matches when the person has turned on a "reduce motion" setting on their device. Line by line:

  1. Lines 1 and 2 make a Stop button and a spinner with the is-spinning class.
  2. Lines 5 to 7: a click on the button removes the is-spinning class.
  3. Line 12: while the spinner has that class, it turns once every 700 ms, forever.
  4. Line 13 is the @keyframes list: turn all the way around.
  5. Line 14: for people who ask for less motion, the spinner turns once, very fast, and stops.

To stop the spinner, you remove a class. There is no JavaScript loop to cancel.

Which animation tool fits?
  • A button grows a little when the mouse is over it
  • A drawer opens and closes when a class changes
  • A loading spinner used on many pages
  • A pulse that repeats three times to get attention
  • A message slides in, with a duration from the user's settings
  • Pause, reverse, and finish a tutorial card's animation
  • A game character moves by speed and bumps into walls
  • Motion that follows how fast the page is scrolled
Try it yourself
0 of 8 correct

Put each task under the tool you would try first.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

element.animate()

PLAYGROUND

The Web Animations API sits between CSS and your own rAF loop. You call element.animate(keyframes, timing):

  • keyframes is a list of styles to move through, like from translateX(0) to translateX(160px).
  • timing is an object with options, like duration in milliseconds and easing.

The browser plays the animation for you, so you don't write a frame loop. It also returns an Animation object right away. You use that object to control the animation from your code:

  • Methods: play(), pause(), reverse(), and finish().
  • Properties: playbackRate is the speed. playState says what it is doing, like "running" or "paused". currentTime is how far in it is, in milliseconds.
  • A finished promise. A promise is an object that tells you later when something is done. This one is done when the animation ends.
card.animate() gets keyframes and timing. It returns an Animation object, drawn as a cart called animation that holds the play() and pause() methods.card.animate()keyframes + timingreturnsplay()pause()animation
card.animate() gets the keyframes and the timing, starts the motion, and returns an Animation object. You call its methods, like play() and pause(), to control it.
Real-life analogyA TV remote

A TV remote doesn't draw the picture on the TV. The TV does that. The remote only lets you play, pause, skip, and see what is happening.

In real life: Press play or pause
In JavaScript: Call play() or pause()
In real life: Skip forward or rewind
In JavaScript: Set currentTime or call reverse()
In real life: Look at whether the show is playing
In JavaScript: Read playState
Web Animations lab: control a moving card
element.animate() returns an AnimationPop out in the code editor (opens in a new tab)JavaScript
const animation = card.animate(  [    { transform: "translateX(0)", opacity: 0.6 },    { transform: "translateX(160px)", opacity: 1 },  ],  {    duration: 900,    easing: "ease-out",    iterations: 1,    direction: "normal",    fill: "forwards",  },); animation.finished.then(() => {  animation.commitStyles();  animation.cancel();});
Animation objectidle
card
playbackRate
1x
reduced motion
no preference
Try it yourself

Press Play to make an Animation object. Each button calls one of its methods. The status shows playState, currentTime, and when the finished promise is done.

fill: "forwards" keeps the end style showing, but finished animations can pile up. commitStyles() saves the end style on the card, and then cancel() removes the animation.
Animate a card, then clean upPop out in the code editor (opens in a new tab)JavaScript
const animation = card.animate(  [    { transform: "translateX(0)", opacity: 0.6 },    { transform: "translateX(160px)", opacity: 1 },  ],  {    duration: 900,    easing: "ease-out",    iterations: 1,    direction: "normal",    fill: "forwards",  },); animation.finished.then(() => {  animation.commitStyles();  animation.cancel();});

Line by line:

  1. Line 1 calls card.animate() and keeps the Animation object in animation.
  2. Lines 3 and 4 are the keyframes: start at translateX(0) with opacity 0.6, and end 160 pixels to the right with opacity 1.
  3. Lines 7 to 10 are the timing: 900 ms, ease-out, played once, in the normal direction.
  4. Line 11, fill: "forwards", keeps the end style showing after the animation ends. Without it, the card would jump back to where it started.
  5. Line 15 waits for the finished promise.
  6. Line 16, commitStyles(), writes the end style into the card's own style attribute.
  7. Line 17, cancel(), removes the animation. The card stays where it ended, because line 16 saved that style.

Where you'll use this

In real work, pick the simplest tool that does the job:

  • A menu that fades in can be a CSS transition, as in the Styles and classes lesson.
  • A tutorial card that must pause when a dialog opens can use element.animate().
  • A canvas game, or a chart you drag through, can use rAF. Keep slow work, like measuring the page layout, out of the frame function.
A checklist for real pages
  • Animate transform and opacity when you can.
  • In rAF, measure the elapsed time. Never assume a frame takes 16.7 ms.
  • Cancel loops and animations when that part of the page is removed.
  • Read the reduced-motion setting in the browser, after the page loads, and offer a calm version.
  • When later code must wait for an animation to end, use animation.finished.
A fade that respects reduced motionPop out in the code editor (opens in a new tab)JavaScript
const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
const duration = fadeDuration(reduceMotion);
const fade = panel.animate(
  [{ opacity: 0 }, { opacity: 1 }],
  { duration, fill: "forwards" },
);
console.log(reduceMotion ? "instant fade" : "400 ms fade");

Line by line:

  1. Line 1 asks the browser whether the person wants less motion. matchMedia() checks a CSS media query, and .matches is true or false.
  2. Line 2 uses fadeDuration, a helper this lesson provides: 1 ms for less motion, 400 ms otherwise.
  3. Lines 3 to 6 fade the panel from opacity 0 to 1 and keep the end style.
  4. Line 7 prints which fade was used.

The panel still appears, so the person still sees that something changed. It just doesn't take a long trip across the screen.

Only the browser knows this setting. The server builds the page before it reaches the browser, so it can't read it. Run code like this in the browser, after the page loads, for example in a click handler.

Common mistakes

MISTAKES
  • "rAF means 60 frames a second." It means "just before the next repaint." A screen might draw 60, 90, or 120 frames a second, a number that changes, or fewer in a hidden tab.
  • "Moving 2 pixels per frame is fine." Then the speed changes from screen to screen, and the motion falls behind after a slow frame.
  • "All CSS animation is free." Animating transform and opacity is often cheap. Animating sizes or positions, like width or top, makes the browser work out the layout again on every frame, and that can be slow.
  • "JavaScript animation runs off the main thread." Normal rAF callbacks, and style changes made from JavaScript, run on the main thread with the rest of your code.
  • "fill: "forwards" cleans up." It keeps the end style showing. It does not remove the finished animation. commitStyles() and then cancel() do that.
  • "Reduced motion means no animation design." It means less movement: instant changes, short fades, or progress in small steps.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict motion by elapsed time

Read the code without running it. Which two values does it print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const timestamps = [0, 16.7, 50];
let position = 0;
let previous = timestamps[0];
for (const timestamp of timestamps.slice(1)) {
  const elapsed = timestamp - previous;
  position += 0.12 * elapsed;
  console.log(position.toFixed(2));
  previous = timestamp;
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict motion by fixed steps

    This version moves a fixed amount per frame. Which two values does it print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const timestamps = [0, 16.7, 50];
    let position = 0;
    for (const timestamp of timestamps.slice(1)) {
      position += 2;
      console.log(position.toFixed(2));
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFind the cleanup bug

      The loop keeps running after Reset. Which line is missing from reset()?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      let rafId;
      function start() {
        rafId = requestAnimationFrame(frame);
      }
      function reset() {
        position = 0;
      }
        Exercise 4 · PracticeChoose the right tool

        Write down which tool you would use for each: an underline that appears on hover, a loading shimmer that repeats, a welcome card the user can pause, and moving particles drawn on a canvas.

          Exercise 5 · ChallengeRespect reduced motion

          Write code that picks calm timing when the person asks for less motion.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const media = window.matchMedia("(prefers-reduced-motion: reduce)");
          // If media.matches, show the final state without long travel.

            Check your understanding

            7 QUESTIONS
            Animation with JavaScript quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Why is requestAnimationFrame usually better than setInterval for motion on the screen?

              Choose an answer to see the explanation.

            2. Question 2 of 7This code moves by elapsed time. What does it print after the late frame?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const timestamps = [0, 16.7, 50];
              let position = 0;
              let previous = timestamps[0];
              for (const timestamp of timestamps.slice(1)) {
                const elapsed = timestamp - previous;
                position += 0.12 * elapsed;
                console.log(position.toFixed(2));
                previous = timestamp;
              }

              Choose an answer to see the explanation.

            3. Question 3 of 7Which cubic-bezier() can CSS use?

              Choose an answer to see the explanation.

            4. Question 4 of 7Which two properties are usually the smoothest to animate?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does element.animate(keyframes, timing) return?

              Choose an answer to see the explanation.

            6. Question 6 of 7What should you watch for with fill: "forwards"?

              Choose an answer to see the explanation.

            7. Question 7 of 7How should a page respect the reduced-motion setting?

              Choose an answer to see the explanation.

            Key takeaways

            • requestAnimationFrame runs your code just before the next repaint. It passes a timestamp that every rAF callback in that frame shares.
            • Moving by elapsed time keeps the speed right after slow frames and on fast screens. Moving a fixed amount per frame does not.
            • A timing function shapes the speed. In cubic-bezier(), the x numbers stay from 0 to 1; the y numbers can go past that.
            • Use CSS for motion you know ahead of time, element.animate() for motion your code builds or controls, and rAF when every frame needs new math.
            • Respect reduced motion, don't start motion on your own, animate transform and opacity, and clean up loops and finished animations.

            JavaScript animation changes what you see one frame at a time: ask the browser for each frame, measure the real time, and pick the simplest tool that gives you the control you need.

            Up next: Drawing with canvas.

            CompleteFrontend Clear concepts. Working examples.