Nothing inside a canvas animation is a real object. The browser holds one bitmap, JavaScript wipes it and paints it again on every frame, and what looks like a ball is only a patch of colored pixels for as long as that frame lasts. Browser games, particle systems, animated charts and generative art all work this way.

MDN Web Docs (2025) lists the canvas element as available across browsers since July 2015, so major browsers have supported it natively for more than 11 years.

What are canvas animations?

JavaScript draws them frame by frame onto an HTML canvas element, and every new frame replaces the previous one.

The element belongs to HTML, but on its own it draws nothing. It supplies a bitmap and a script does all the painting.

Developers call this immediate mode rendering. The browser keeps the pixels and forgets what made them, so no scene graph stores a sprite or a chart bar.

To move a ball, the script clears the old pixels and paints the ball again at its new position.

The WHATWG HTML Living Standard sets the starting state. A new canvas is a transparent black bitmap, 300 pixels wide and 150 pixels high, unless the width and height attributes say otherwise (they’re measured in CSS pixels). It has no rendering context at all until a script calls getContext().

From there JavaScript requests a context and draws onto it, shapes and images mostly, text when needed, and then does the whole thing again next frame.

Apple introduced the element in 2004 for Dashboard widgets. Engineer Dave Hyatt described it then as an image element that accepts drawing commands from code, as Eric Meyer’s 2004 coverage records.

The word “canvas” also names unrelated products, such as learning platforms and design editors, plus some ad builders that have a timeline canvas. None of those are meant here, only the browser technique.

How does a canvas animation loop work?

Every frame starts by wiping the last one with clearRect(). Skip that and the old pixels stay on screen as trails.

Where is web design headed next?

Discover the latest web design statistics: industry growth, design trends, technology adoption, and insights defining the future of the web.

Explore the Data →

Next the script updates its state, meaning positions, velocities and colors held in plain JavaScript variables. Then it paints every object in its new state through the drawing context. The last step is calling requestAnimationFrame() again, because each call fires only once.

MDN Web Docs describes requestAnimationFrame() as a request for the browser to call your function before the next repaint. A callback that skips the re-request ends the animation after one frame.

setInterval runs on its own clock, unrelated to when the display repaints. Educative’s canvas guide names requestAnimationFrame as the option that gives smoother motion.

Keep update and draw in separate functions. That split is the core of any JavaScript game loop, and it lets input or physics code change state without touching drawing code.

Take a bouncing ball. Update adds a velocity to x and y and flips its sign at the canvas edges. Draw paints one filled circle.

Why animation speed changes across screens, and how delta time fixes it

requestAnimationFrame fires once per screen refresh, so an object that moves a fixed distance per callback covers more ground each second on a 144 Hz display than on a 60 Hz one.

MDN Web Docs says callback frequency generally matches the display refresh rate. It names 60 Hz as the most common, with 75, 120 and 144 Hz also widely used.

Say the step is 4 pixels per callback. That works out to 240 pixels per second at 60 Hz and 576 at 144 Hz. Multiply a speed of 240 pixels per second by the elapsed seconds instead, and you get 240 pixels per second at any refresh rate.

The gap between the current callback’s timestamp and the previous one is the delta time. Multiply every speed by it and movement follows the clock instead of the frame count.

MDN Web Docs warns about this directly. Derive per-frame progress from the timestamp argument, or the animation runs faster on high refresh-rate screens.

Delta time breaks in two places. The first frame has no previous timestamp, so skip that frame’s update. After a stall, like a sleeping tab or one very long frame, the gap is huge and objects jump across the canvas, so clamp the value to a maximum.

The frame budget is 1000 ms divided by the rate, which gives 33.33 ms at 30 frames per second, 16.67 ms at 60, 8.33 ms at 120 and 6.94 ms at 144. Update and draw both have to finish inside that window, or frames drop.

Which drawing context to use: 2D, WebGL, or WebGPU

Start with the 2D context. Move to WebGL or WebGPU only when object count or shader effects push each frame past its time budget. I’d say 2D is enough more often than people expect.

2D context

getContext(“2d”) returns a CanvasRenderingContext2D. It draws paths, rectangles, text and images, and drawImage() copies regions from a sprite sheet. No library needed.

Charts and sprite animations fit here without extra tooling.

WebGL and WebGL2

WebGL adds speed and costs code.

The WebGL context hands drawing to the GPU through shader programs, which MDN Web Docs describes as high-performance rendering of 2D and 3D graphics. Shaders are written in GLSL ES, a separate language from JavaScript.

WebGL 2 is based on OpenGL ES 3.0 and adds instanced rendering, which draws many copies of one shape in a single call. The Khronos Group (2022) announced support for it in all major browsers.

WebGPU

WebGPU, a W3C standard, now ships in all major browsers, and Safari’s September 2025 release closed the last gap.

web.dev (November 2025) lists the versions that introduced support as Chrome and Edge 113, Firefox 141 on Windows, and Safari 26. Coverage is still uneven by platform. Firefox 145 added macOS 26 on Apple Silicon and Firefox 147 extended support to all macOS versions, but Linux and Android are not yet in stable Firefox. Chromium on Linux is limited to certain GPUs (Intel Gen12 and newer from Chrome 144, NVIDIA on Wayland from Chrome 147), and on Android it needs Android 12 or later and a supported GPU (Chrome 121), per the gpuweb Implementation Status wiki.

Three.js shows the practical pattern. Its WebGPURenderer uses a WebGPU backend by default and falls back to WebGL 2 when the browser lacks it, per the Three.js manual.

The same manual notes that some scenes still run faster, or support more features, on the older WebGLRenderer.

Types of canvas animations and what they are used for

Sprite animations, particle systems, procedural animations and 3D work through WebGL cover most of what people build on canvas.

Sprite animations

The frames sit on one image called a sprite sheet. Each tick, drawImage() copies a different source rectangle onto the canvas.

Frame changes run on their own clock, slower than the screen refresh. Phaser, which MDN Web Docs lists as a framework for Canvas and WebGL browser games, lets game code work with sprites instead of raw drawImage() calls.

Particle systems

Each particle is a small object holding a position and a velocity, plus a lifetime.

The loop moves every particle each frame and removes the expired ones. When counts outgrow the 2D context, the instanced rendering covered above moves the drawing to the GPU.

Procedural animations

Formulas replace stored frames.

MDN’s canvas tutorial builds a clock that reads the system time and redraws the hands each frame, plus a stream of colored light that follows the mouse pointer. Creative coding libraries such as p5.js wrap the same style of drawing.

3D animations

Vertices and shaders go to the GPU through a WebGL context. Three.js, covered above, is the common library for that work.

D scenes need shader and camera concepts that 2D sprite work never asks for, which is a real jump in difficulty.

Where canvas animations show up

Browser games run sprites, particles and collision checks on the display refresh loop. Animated charts get drawn on a single canvas. Generative art redraws procedural patterns every frame.

The Chart.js documentation states that it renders on canvas instead of SVG, which avoids thousands of DOM nodes for large datasets. The same page notes that canvas rendering disallows CSS styling, so built-in options replace style sheets.

Canvas animations vs SVG vs CSS animations

The three overlap more than people expect, and the choice mostly comes down to object count and surface size.

DimensionCanvasSVGCSS animations
ModelPixels, redrawn by scriptShapes in the DOMDeclarative styles on DOM elements
Best fitMany objects, smaller surfaceFewer objects, larger surfaceUI transitions, simple motion
EventsHand-built hit testingNative per shapeStandard DOM events
Modified byDraw calls onlyDOM, script and CSSStyle rules

Microsoft’s Internet Explorer 9 documentation draws the same line, with canvas for a smaller surface or a larger number of objects. SVG gets the opposite case, a larger surface or fewer objects.

Sources don’t fully agree here. LogRocket’s benchmark tally puts canvas ahead of SVG at every point it measured.

Microsoft’s guidance and Boris Smus’s benchmark both qualify that result, because SVG holds up better as the drawing surface grows. Smus found that canvas render time degrades roughly linearly with pixel count, while SVG is unaffected by the size of the drawing area and instead slows as the number of objects rises. The results differ because the tests vary different things. LogRocket changes object count and object size, while the other two bring in surface area.

For motion on regular page elements, CSS animation often needs no canvas at all. web.dev notes that CSS animations, and many transform or opacity changes, are typically handled on the compositor thread, so they keep running while the main thread is busy.

In practice it shakes out like this.

  • Many moving objects on a modest surface, with no per-object accessibility needs, point to canvas.
  • Few objects on a large or zoomable surface, or any need for per-shape events and CSS styling, point to SVG.
  • Hover, reveal and slide effects on elements already in the page usually belong to CSS animations.

How to keep canvas animations fast

Speed comes down to doing less work per frame. Every fix below protects the frame budget.

Reduce the area redrawn

Most scenes keep a static background, so redraw only what changed.

Dirty rectangles clear and repaint just the bounding box of objects that moved. Layered canvases stack separate canvas elements for a static background, the game layer and the interface layer, so static content isn’t redrawn on every tick (MDN Web Docs). For a large static background image, MDN Web Docs separately suggests setting it as a CSS background on a div placed under the canvas.

Dirty rectangles lose their edge when most objects move each frame, because the bookkeeping costs more than a full clear.

Reuse pre-rendered images

  • Draw complex or repeated shapes once onto an offscreen canvas, then copy that canvas each frame with drawImage().
  • Store scaled versions of images at load time instead of scaling inside drawImage() on every frame.
  • Whole-number coordinates skip the extra anti-aliasing work that sub-pixel rendering forces.
  • Batch similar calls, and avoid unneeded changes to the context state.
  • getContext(“2d”, { alpha: false }) tells the browser no translucency is needed.

All five come from MDN’s Optimizing canvas guide.

Move rendering off the main thread

MDN Web Docs marks transferControlToOffscreen() as Baseline since March 2023, which means it works across current browsers and devices.

The method hands a canvas to a worker, which runs its own requestAnimationFrame() loop and draws away from the main thread. The worker cannot read the DOM, so input events reach it through postMessage().

Why bother? web.dev says CSS animations typically run on the compositor thread and survive main-thread stalls. A canvas loop on the main thread gets no such protection.

The messaging code only pays off when drawing itself is the heavy part.

Scale for high-density screens

Sharp output costs pixels.

MDN Web Docs sizes the canvas bitmap as the CSS size multiplied by the device pixel ratio, then sets the CSS width and height back to the CSS size. At a ratio of 1, a 1,000 by 600 CSS pixel canvas holds 600,000 pixels. At a ratio of 2 the same canvas holds 2,400,000, which is 4 times the drawing work per frame.

Capping the ratio at 2 trades a little sharpness for budget.

Profile before changing anything

Turn on the FPS meter in the Chrome DevTools Rendering tab first, which reports the percentage of frames delivered. web.dev’s animation guide uses 99% of frames delivered as an example of a healthy result. Then record the animation in the Performance panel to see where the time goes.

When canvas animations are the wrong choice

Skip canvas when content must be readable by assistive technology, selectable as text or hit-tested per object, and when the canvas would exceed a device’s size limit.

The WHATWG HTML Living Standard tells authors to pick a more suitable element whenever one exists.

Accessibility gaps

Canvas content is not exposed to assistive technology the way semantic HTML is, so web accessibility needs planning before the first frame is drawn.

The first fix is HTML between the canvas tags. Screen readers read it, and browsers show it when JavaScript is off or canvas is unsupported (MDN Web Docs).

ARIA attributes on the canvas element, such as role and aria-label, name an interactive control. drawFocusIfNeeded() paints a focus ring when the linked fallback element has focus.

Beyond display, put real buttons and controls in the DOM and let the canvas only draw.

Motion that cannot be stopped

WCAG 2.2 Success Criterion 2.2.2 (Level A) requires a pause, stop or hide control for motion that starts automatically, lasts more than five seconds and sits beside other content.

A looping canvas background meets all 3 conditions. Add a toggle, and check the prefers-reduced-motion media query before the loop starts.

Per-object interaction and text

On canvas, the script has to work out which shape sits under the pointer. SVG fires events natively on each shape.

Microsoft’s Internet Explorer developer documentation (“WebGL, Canvas, or SVG? Choose the right API”) makes the same point. Canvas objects cannot carry their own event handlers, and click positions are not natively converted to game coordinates, while SVG fires events natively. Text-heavy interfaces and forms belong in HTML for the same reason.

Size limits

MDN Web Docs says most browsers allow canvases beyond 10,000 by 10,000 pixels, but iOS devices cap them at 4,096 by 4,096 pixels. Past the limit the canvas becomes unusable and drawing commands stop working.

The pixel scaling above makes the cap easy to hit. A 1,400 CSS pixel canvas at a ratio of 3 needs 4,200 pixels across.

Plain JavaScript or a library for canvas animations?

Plain JavaScript is enough for one or two effects. Add a library when the project needs object events, asset loading or scene management. One brought in for a single effect adds weight nothing uses.

Plain code suits a background effect, a single animated chart or a learning project.

Konva adds the retained scene graph that canvas itself lacks. Its stage, layers, groups and shapes act as virtual nodes similar to DOM nodes, and each layer renders a visible scene canvas plus a hidden hit canvas for event detection, per the Konva documentation. Disabling the hit graph speeds up drawing but turns off mouse and touch detection on that layer.

PixiJS targets throughput. Its renderers draw to a canvas through WebGL or WebGL2 by default, WebGPU as an option, and the 2D context as a fallback.

PixiJS’s v8 rendering guide calls the WebGPU renderer feature-complete, but warns that inconsistencies between browser implementations may cause unexpected behavior, and it advises the WebGL renderer for production. Broad WebGPU availability and consistent WebGPU behavior are separate questions.

Which one to pick depends on what’s missing. Konva covers object events and selection, and PixiJS handles many sprites at high frame rates. A complete game with physics and tilemaps points to Phaser. Generative sketches go to p5.js.

Add a library the day plain code starts to reimplement one of these.

Canvas Animations FAQ

Can canvas animations run in React?

Yes. Keep the canvas in a ref, start the requestAnimationFrame loop inside useEffect, and call cancelAnimationFrame in the cleanup function. Without the cleanup, the loop keeps running after the component unmounts.

Do canvas animations work in all browsers?

Yes, for the 2D context. WebGL is different, because MDN Web Docs notes the device needs supporting GPU hardware as well as a capable browser.

Test GPU-based animations on real devices and keep a 2D fallback.

Why does a canvas animation flicker?

Flicker usually means the browser painted a frame that was cleared but not fully redrawn. Finish clearing and drawing inside one requestAnimationFrame callback.

Load images before the loop starts, so nothing draws asynchronously mid-frame.

Does a canvas animation pause in a background tab?

Yes. MDN Web Docs notes that requestAnimationFrame() calls are paused in most browsers when running in background tabs or hidden iframes, so the loop waits until the tab is visible again.

The first frame after the return carries a large time gap, so clamp delta time.

How can a canvas animation be exported as video?

Call captureStream() on the canvas and pass the stream to a MediaRecorder, which produces a video Blob. The format depends on the browser, and WebM is common. MDN Web Docs documents an optional frame rate argument that sets the capture rate. If it is unset, a new frame is captured whenever the canvas changes.

Video suits longer or higher-resolution animations better than GIF.

How do canvas animations react to mouse and keyboard input?

Add pointer or key listeners, write the values into plain variables, and let the next update step read them. MDN’s canvas tutorial uses this pattern for a mouse-following animation.

A canvas receives keyboard events only when it can take focus, which tabindex=”0″ enables.

What to Build First to Test Canvas Animations

Build the first prototype with the project’s real object count on the slowest target device, using the 2D context and a delta-time loop.

Measure it before adding anything else. Optimization code costs maintenance, and only a profile shows which fix the project needs. Profile the result against the frame budget, then add layers, OffscreenCanvas or a GPU context only for the bottleneck that actually shows up.

Moving to a GPU context later means rewriting the 2D drawing code, so the first version is partly throwaway.

The WebGPU and library-renderer guidance above was verified in October 2026. The advice to prefer the WebGL renderer over WebGPU for production changes once library documentation stops recommending it.

Projects that grow into games continue with HTML game development.

Bogdan Sandu
Latest posts by Bogdan Sandu (see all)