Skip to content
explainroo
FFmpeg and rendering

HTML Canvas Animation: How to Animate with JavaScript

Updated 4 min read

On this page
  1. How canvas animation works
  2. Create a moving circle
  3. Keep the animation smooth and readable
  4. Make a narrated canvas video with explainroo

To animate an HTML canvas, JavaScript clears the drawing area, moves an object, and draws it again in a loop. The <canvas> element creates a blank area on a web page. JavaScript draws shapes and moves them. This works well for custom graphics, interactive effects, and animated diagrams.

For a narrated explainer video made from animated canvas scenes, explainroo uses an HTML canvas to draw video frames and syncs each scene with the narration. For an animation you control on a website, the example below shows the basic JavaScript loop and how to keep its speed consistent.

How canvas animation works

Canvas draws pixels instead of storing a separate, editable object for each shape. JavaScript uses a drawing context with commands to draw on and clear the canvas. The browser does not redraw shapes when their properties change, so the animation code must redraw the scene.

Each animation frame has three steps: clear the old image, update the animation, and draw the new image. The browser repeats these steps with requestAnimationFrame(), which schedules a drawing callback before the next screen update. It pauses callbacks in inactive tabs and draws no more than once per screen update. For most canvas animations, it works better than a timer loop. See canvas animation timing and browser animation methods.

Create a moving circle

Save this as an HTML file and open it in a browser. The canvas fills its container. A circle moves across it and bounces off the edges.

<canvas id="stage" aria-label="A circle moving across the canvas">
  A circle moves across the canvas.
</canvas>

<style>
  #stage {
    display: block;
    width: 100%;
    height: 240px;
    background: #f2f5f8;
  }
</style>

<script>
  const canvas = document.querySelector("#stage");
  const context = canvas.getContext("2d");

  let width = 0;
  let height = 0;
  let x = 40;
  let y = 120;
  let velocityX = 180;
  let previousTime;

  function resizeCanvas() {
    const bounds = canvas.getBoundingClientRect();
    const pixelRatio = window.devicePixelRatio || 1;

    width = bounds.width;
    height = bounds.height;
    canvas.width = Math.round(width * pixelRatio);
    canvas.height = Math.round(height * pixelRatio);

    // Draw in CSS pixels while storing enough pixels for the display.
    context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
    y = height / 2;
  }

  function drawFrame(time) {
    if (previousTime === undefined) {
      previousTime = time;
    }

    // Limit a long pause so the circle does not jump after a stalled frame.
    const elapsed = Math.min((time - previousTime) / 1000, 0.05);
    previousTime = time;

    const radius = 18;
    x += velocityX * elapsed;

    if (x < radius || x > width - radius) {
      velocityX = -velocityX;
      x = Math.max(radius, Math.min(width - radius, x));
    }

    context.clearRect(0, 0, width, height);
    context.beginPath();
    context.arc(x, y, radius, 0, Math.PI * 2);
    context.fillStyle = "#2864dc";
    context.fill();

    requestAnimationFrame(drawFrame);
  }

  window.addEventListener("resize", resizeCanvas);
  resizeCanvas();
  requestAnimationFrame(drawFrame);
</script>

The animation moves the circle according to elapsed time, not a fixed distance per frame. This keeps its speed consistent across different display frame rates. In the code, velocityX is measured in canvas pixels per second.

The canvas has a displayed size in CSS pixels and internal pixel dimensions. Setting the internal dimensions with devicePixelRatio keeps drawings sharp on high-density displays. The setTransform() call lets the drawing code keep using CSS-pixel coordinates.

Keep the animation smooth and readable

Clear and redraw the whole canvas for a simple scene like the moving circle. For an animation that draws more and more points, such as a brush stroke, redrawing every point on every frame can slow the animation. Instead, draw each new point as it arrives and use requestAnimationFrame() to separate pointer-event collection from drawing. The canvas brush performance discussion describes this approach.

Draw shapes with the same fill or stroke style together before changing styles. Repeated style changes add work, and fractional coordinates can trigger extra smoothing. For a simple diagram, draw related shapes together and use whole-number coordinates where they look right. These are among the performance techniques for canvas.

For responsive, accessible pages, resize the canvas when its container changes and put equivalent text or controls in the page’s HTML for content that matters to users. Screen readers cannot access text and controls drawn as canvas pixels as they can ordinary page elements. The comparison of animation technologies explains these tradeoffs.

Make a narrated canvas video with explainroo

For a narrated explainer video rather than an interactive web page, explainroo handles the video work around the canvas animation. An AI coding agent writes script.md with the narration and scenes.js with the JavaScript that draws each scene. explainroo renders the scenes on an HTML canvas, syncs them to spoken words, and produces an MP4.

To begin, give a coding agent this prompt and replace the bracketed topic:

Make me a short explainer video about [your topic]. Use explainroo for it: clone the project, read its AGENTS.md and follow the steps.

The agent sets up explainroo and creates the files. It then checks the video and gives you the finished MP4. explainroo works with Claude Code, Codex, Pi, OpenCode, Gemini CLI, and other coding agents that can run shell commands. It works best with Claude Code. Its documentation explains the setup and options.

Canvas scenes can include charts, code, icons, hand-drawn-style lines, or screenshots. explainroo uses an open voice model to narrate in English and syncs the visuals to the spoken words. It also checks scene images and text layout, and checks whether the voice pronounced words correctly. View example videos to see the results.

explainroo is free and open source under the MIT license. Optional AI illustrations have a per-image cost, and the coding agent is a separate service with its own pricing. The tool makes narrated explainers and product demos. It does not edit existing camera footage or create talking avatars. See the explainroo project for its code and setup details.

Free and open source

Let your AI agent make the video

explainroo lets a coding agent like Claude Code or Codex make narrated explainer videos and product demos. The voice, the word timing and the rendering run on your own computer, with no API key and no cost per video.

Copy this into your AI agent

Make me a short explainer video about [your topic]. Use explainroo for it: clone https://github.com/vincentsch/explainroo, read its AGENTS.md and follow the steps.

Getting started Example videos GitHub

Made with explainroo: How a cache makes a website faster

More on ffmpeg and rendering

All guides on ffmpeg and rendering