Canvas Noise Texture Background — Free Animated Film Grain Overlay

Canvas Noise Texture Background · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Direct ImageData writes
Random grayscale bytes written straight to the pixel buffer.
No shape-drawing overhead
putImageData blits the whole frame in one call.
Deliberately low resolution
35% backing-store size, upscaled by CSS for free.
Throttled refresh rate
12fps regeneration reads as texture, not flicker.
Blend-mode compositing
overlay blend interacts with underlying colors and gradients.
Reduced-motion aware
A static frame replaces the interval when requested.
Click-through overlay
pointer-events: none keeps content beneath fully interactive.
Zero dependencies
Pure Canvas 2D and vanilla JS.

About this UI Snippet

Canvas Noise Texture Background — Raw Pixel Grain Over Dark Sections

Screenshot of the Canvas Noise Texture Background snippet rendered live

Film grain is the texture that makes a flat dark section feel like it has depth and material to it — the difference between a plain black background and one that looks like it was shot on film. This snippet generates that grain by writing random grayscale values directly into a canvas ImageData buffer, rather than drawing thousands of individual shapes, and throttles the refresh rate so it reads as texture instead of strobing static.

Writing pixels directly instead of drawing shapes

paintNoise() creates an ImageData object sized to the canvas and walks its underlying Uint8ClampedArray four bytes at a time — red, green, blue, alpha — setting R, G, and B to the same random 0-255 value (grayscale) and alpha to fully opaque. ctx.putImageData() then blits that entire buffer to the canvas in one call. This is dramatically cheaper than the equivalent ctx.fillRect() per pixel would be, because it bypasses the canvas's normal shape-drawing pipeline entirely and writes memory directly.

Small canvas, upscaled by CSS

Grain doesn't need to be high-resolution to read as grain — random noise looks like random noise at almost any pixel density. resize() deliberately sizes the canvas backing store to 35% of the wrapper's dimensions, and CSS stretches it to fill the section. That keeps the ImageData allocation and putImageData write small and fast, since both scale with total pixel count.

Throttled refresh, not 60fps

Regenerating fresh noise on every requestAnimationFrame tick would refresh 60 times a second — fast enough to read as flickering static rather than texture, and wasteful for a background element. Instead, setInterval(paintNoise, 1000 / 12) refreshes at a deliberately low 12fps, which is fast enough to feel alive and un-static but slow enough to stay a background texture rather than a distraction.

Compositing, not just opacity

The CSS sets mix-blend-mode: overlay alongside a moderate opacity, so the grayscale noise blends with whatever color is beneath it — lightening dark areas and darkening light ones — rather than simply sitting on top as a flat gray haze. That combination is what makes the effect read as texture *in* the section rather than a layer *over* it. It also respects prefers-reduced-motion, painting a single static frame and skipping the interval entirely when that preference is set. Pair it with a noise background variant or a canvas mesh gradient for a more colorful companion texture.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain why writing directly into an ImageData buffer's Uint8ClampedArray and calling putImageData is so much cheaper than issuing one fillRect per pixel, and why throttling the regeneration interval to around 12fps instead of every requestAnimationFrame tick still reads as convincing film grain. It's also useful for understanding the compositing choice — ask what mix-blend-mode: overlay actually does mathematically compared to plain opacity, and why that specific blend mode is what makes the grain look embedded in the surface rather than laid over it. For extensions, ask it to add slight color tinting to the noise (sepia or cool-blue grain instead of pure grayscale), vary the noise intensity based on scroll position or a hovered state, or add a subtle vignette drawn once alongside the grain generation. It can also help you reason about the resolution/performance tradeoff — ask how low the 0.35 scale factor could go before the grain starts to look like visible large blocks instead of fine texture. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "canvas noise texture background" effect in plain HTML, CSS, and JavaScript using the Canvas 2D API's ImageData interface — a subtle animated film-grain overlay for a dark hero section, no libraries.

Requirements:
- An absolutely positioned canvas overlaying a dark hero section, styled with pointer-events: none (so it never blocks interaction), a moderate opacity, and mix-blend-mode: overlay so the grain visually interacts with the colors beneath it instead of sitting on top as a flat gray layer.
- Size the canvas's drawing backing store to a noticeably reduced fraction of the wrapper's actual pixel dimensions (e.g. roughly 30-40%), relying on CSS to stretch the canvas element to full size — explain in a comment why noise texture doesn't need high resolution to read convincingly.
- Write a function that generates one frame of grayscale noise by creating an ImageData object sized to the canvas, looping over its underlying pixel array 4 bytes at a time, and setting each pixel's R, G, and B channels to the same random 0-255 value (with alpha at 255), then blit it to the canvas in one ctx.putImageData call — do not use fillRect or any shape-drawing API for the individual grain pixels.
- Do not regenerate the noise on every requestAnimationFrame tick. Instead, throttle regeneration to a low, explicit frame rate (roughly 10-15fps) using setInterval, and explain why a full 60fps refresh would read as flicker rather than settled texture while wasting CPU.
- Check window.matchMedia('(prefers-reduced-motion: reduce)') on load: if it matches, paint a single static noise frame and skip starting the interval entirely, so the texture is still present for users who have asked for less motion without a perpetually regenerating animation running.
- Style the surrounding page as a dark, editorial/cinematic hero section with a serif heading, supporting text, and an outlined button, so the grain has a moody dark backdrop to add texture to.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA subtle grain layer overlays the dark hero section immediately.
  2. 2
    Look closely at the textureGrain refreshes about 12 times per second, not every frame.
  3. 3
    Check the blend modemix-blend-mode: overlay lets grain interact with colors beneath it.
  4. 4
    Resize the windowThe small backing-store canvas rescales and keeps refreshing.
  5. 5
    Enable reduced motionThe interval is skipped; one static grain frame renders instead.
  6. 6
    Tune the densityChange the 0.35 resolution factor or the FPS constant.

Real-world uses

Common Use Cases

Dark hero sections
Add tactile texture behind a headline and CTA.
Editorial / film / photography brands
A literal grain motif for a cinematic aesthetic.
Alongside mesh gradients
Layer over canvas mesh gradient for grit.
Album / release landing pages
A moody backdrop for a music or media drop.
Premium/luxury product pages
Subtle texture reads as material and craft.
Companion to noise-background
An animated alternative to noise background.

Got questions?

Frequently Asked Questions

ctx.createImageData gives direct access to the canvas's underlying Uint8ClampedArray, so setting a pixel is just writing four numbers (R, G, B, A) to an array index, and ctx.putImageData blits the entire buffer to the canvas in a single call. Issuing a fillRect (or arc) per pixel instead would go through the canvas's full shape-drawing and compositing pipeline for every single pixel, which is dramatically slower at any meaningful resolution.

Regenerating fresh random noise 60 times a second (once per requestAnimationFrame tick) reads as flickering static rather than a settled texture, and does so while burning CPU for no additional visual benefit as a background element. Throttling the regeneration to roughly 12fps via setInterval keeps the grain feeling alive without the strobing quality of a full-rate refresh, and uses a fraction of the CPU.

Random noise looks like random noise at essentially any pixel density, so there is no visual benefit to generating it at full viewport resolution. Sizing the canvas backing store to about 35% of the wrapper and letting CSS stretch the element to fill it keeps both the ImageData allocation and the putImageData write small, since their cost scales directly with total pixel count.

Opacity alone would just lay a flat translucent gray haze on top of the section, which reads as a layer sitting over the content rather than texture within it. overlay blend mode instead combines the grayscale noise with whatever color is directly beneath each pixel — lightening darker areas and darkening lighter ones — so the grain appears to be part of the surface itself rather than an added film on top of it.

Move the resize() call and the paintNoise() interval setup into a mount effect, storing the interval id so it can be cleared in the cleanup function on unmount. Since the effect draws to a canvas ref rather than relying on requestAnimationFrame, there's no animation frame to cancel — just make sure the interval is torn down so it doesn't keep running against a canvas element that no longer exists in the DOM.