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.
Step-by-step resolution
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-filterabove 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.
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.
playsinlineandmutedare required for autoplay on mobile, which limits what video can be used for.- Media elements hidden with
display: nonerelease 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.
Related
- GPU Memory & Texture Management — the memory model behind these costs
- Image Decoding and Animation Jank — the still-image equivalent
- Pausing Off-Screen and Background-Tab Animations — stopping media nobody sees