Video and Canvas Layer Memory Costs

Part of GPU Memory & Texture Management in Performance Budgeting & GPU Architecture.

The problem

A landing page has an autoplaying background video, a canvas particle effect behind the hero, and a product grid whose cards animate on hover. On a mid-range phone, hovering the cards is smooth on an empty page and stuttery on this one. Scrolling is worse. Memory warnings appear in the console, and occasionally the video goes black for a frame before recovering.

Nothing in the card animation changed. What changed is how much GPU memory the page already holds before the animation starts.

Root cause analysis: media always occupies a layer

Video is always composited. A playing video element is decoded into frames that are handed to the compositor as textures. It occupies a layer for as long as it is rendered, sized to its displayed area times the device pixel ratio, and the decoder keeps several frames buffered. A full-viewport background video on a high-density phone is one of the largest single memory consumers on a page.

Canvas holds a backing store. A canvas element allocates a bitmap of its width by height attributes — not its CSS size — multiplied by four bytes per pixel, plus a texture for compositing. A canvas styled to 800 by 400 CSS pixels but given attributes of 3200 by 1600 for a high-density screen holds about 20MB, and gains nothing on a device whose ratio is 2.

Effects over media are expensive. backdrop-filter above a playing video forces the region behind the effect to be re-read and re-blurred every frame the video updates, which is every frame. Blend modes above video have similar costs. Both turn “video plays on the compositor” into per-frame work.

Animating around media is cheap; animating media is not. Moving or fading a video or canvas element with transform and opacity is ordinary compositor work. Redrawing canvas contents each frame is main-thread work proportional to what you draw, and resizing a canvas reallocates its backing store — an expensive operation that also clears it.

Pressure causes eviction. When GPU memory runs short, layers are evicted and re-rastered, which is the flicker described in texture memory budget for mobile.

GPU memory held by media on a 412 by 915 phone at DPR 2.6Approximate texture and buffer footprints while visible.GPU memory held by media on a 412 by 915 phone at DPR 2.6Full-viewport background video22 MBCanvas at DPR 2.6, hero size12 MBSame canvas capped at DPR 27 MBPoster image instead of video3 MB
Approximate texture and buffer footprints while visible.

Step-by-step resolution

Budgeting media on an animated pageFewer, smaller, paused when unseen.Budgeting media on an animated page1List every video and canvas visible at once with its pixel size.A memory inventory2Cap canvas backing stores at a device pixel ratio of about 2.Large saving, little visible difference3Remove backdrop-filter and blend modes above playing video.No per-frame re-blur4Pause video and canvas loops when off-screen.Memory and CPU released5Animate media elements with transform and opacity only.No redraw or reallocation6Swap decorative video for its poster under reduced motion.
Fewer, smaller, paused when unseen.

Production code pattern

// Canvas sized from CSS with a capped pixel ratio; resized only when it really changes.
function sizeCanvas(canvas) {
  const dpr = Math.min(window.devicePixelRatio || 1, 2);      // cap: 3x costs 2.25x the memory of 2x
  const rect = canvas.getBoundingClientRect();
  const w = Math.round(rect.width * dpr);
  const h = Math.round(rect.height * dpr);
  if (canvas.width === w && canvas.height === h) return;      // reallocation clears the canvas
  canvas.width = w;
  canvas.height = h;
  canvas.getContext('2d').scale(dpr, dpr);
}

// Pause media that nobody can see.
const io = new IntersectionObserver((entries) => {
  for (const { target, isIntersecting } of entries) {
    if (target.tagName === 'VIDEO') isIntersecting ? target.play().catch(() => {}) : target.pause();
    else target.dispatchEvent(new CustomEvent(isIntersecting ? 'loop:start' : 'loop:stop'));
  }
}, { rootMargin: '100px' });
document.querySelectorAll('video[data-ambient], canvas[data-loop]').forEach((el) => io.observe(el));
/* Animate the media element, never its contents. */
.hero__video {
  transition: scale var(--motion-move-duration) var(--motion-move-easing);
}
.hero:hover .hero__video { scale: 1.02; }

/* No backdrop-filter above playing video: use a solid or translucent colour. */
.hero__caption {
  background: rgb(0 0 0 / 0.45);      /* was backdrop-filter: blur(12px) */
}

@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }     /* poster image shown instead */
  .hero { background-image: url("hero-poster.avif"); background-size: cover; }
  .hero__video, .hero:hover .hero__video { transition: none; scale: 1; }
}

Rendering Impact: composite for the hero scale; removing backdrop-filter above the video removes a per-frame blur of a large region. Capping the canvas ratio halves its memory on three-times-density screens with no visible difference at typical viewing distances.

Guarding the canvas resize with a dimension check matters because assigning width or height reallocates the backing store and clears the canvas even when the value is unchanged — a common source of flicker in resize handlers.

Media and animation combinationsAnimate around media; keep effects off it.Media and animation combinationsCostVerdicttransform on a videoelementCompositeFineopacity on a canvaselementCompositeFinebackdrop-filter overplaying videoPer-frame blur of a large regionAvoidResizing canvas perframeReallocation and clearAvoidSeveral videosvisible at onceMany large texturesLimit to one
Animate around media; keep effects off it.

Video as an alternative to long animations

The comparison runs the other way too. A long, complex decorative sequence — a twenty-second illustrated loop built from dozens of animated SVG layers — can cost more per frame than a short video of the same thing, because video decoding is hardware-accelerated on most devices while SVG paint is not. If the sequence is genuinely decorative, is not interactive and does not need to respond to state, a compressed, muted, playsinline video with a poster is often the cheaper option on low-end hardware. It also has a simpler reduced-motion story: show the poster.

Verification checklist

Constraints and trade-offs

  • Capping the pixel ratio makes canvas drawings slightly softer on very high-density screens.
  • Pausing and resuming video can cause a brief black frame on some devices.
  • Replacing SVG animation with video loses crispness at large sizes and cannot adapt to theme changes.
  • playsinline and muted are required for autoplay on mobile, which limits what video can be used for.
  • Media elements hidden with display: none release their layers, but re-showing them costs a new decode.

Frequently asked questions

Does a video element always use GPU memory?

While it is rendered, yes: decoded frames are handed to the compositor as textures, sized to the displayed area times the device pixel ratio.

How should I size a canvas for high-density screens?

Set the width and height attributes from the CSS size multiplied by the device pixel ratio, capped at about 2, and scale the drawing context to match.

Why is backdrop-filter over video so expensive?

The blur must be recomputed for every video frame, because the content behind it changes continuously.

Is a video cheaper than a complex animated SVG?

Often, for long decorative sequences, because video decoding is hardware-accelerated while SVG paint is not. Interactive or theme-aware graphics should stay as SVG.