webdevpuneet.com

Canvas Snow Overlay — Free Drifting Snowflake Hero Background

Canvas Snow Overlay · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations

Copy & paste

Source Code

<section class="sno-wrap">
  <canvas class="sno-canvas" id="snoCanvas"></canvas>
  <div class="sno-content">
    <span class="sno-tag">canvas overlay · 60fps</span>
    <h1>Winter sale</h1>
    <p>A transparent, click-through snow layer drifting over any hero section.</p>
    <button class="sno-btn">Shop the collection</button>
  </div>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Click-through overlay
pointer-events: none keeps the UI beneath fully interactive.
Per-flake wind phase
Independent sine drift avoids a mechanical, synced sway.
High-DPI aware
devicePixelRatio scaling keeps flakes crisp on Retina.
Allocation-free recycling
Flakes reset in place instead of being spliced and re-added.
Varied size and opacity
Random radius and alpha give a sense of depth.
Reduced-motion fallback
A static frame draws when the OS asks for less motion.
Responsive canvas
Resizes and rescales its backing store on window resize.
Zero dependencies
Pure Canvas 2D and vanilla JS.

About this UI Snippet

Canvas Snow Overlay — Drifting Snowflakes Over Any Section

Screenshot of the Canvas Snow Overlay snippet rendered live

A snow overlay is one of the simplest particle effects to build and one of the easiest to get wrong — flakes that all fall at the same speed in the same straight line read as a bug, not weather. This snippet layers a transparent, non-blocking canvas over a hero section and gives each flake its own size, fall speed, drift rhythm, and opacity so the whole thing reads as gentle, ambient snowfall rather than a repeating sprite.

A transparent, click-through layer

The canvas is positioned absolute; inset: 0 inside a relatively-positioned wrapper, sized to match it exactly, and set to pointer-events: none. That last property is what makes it an overlay rather than an obstacle — every click and hover on the button and text underneath passes straight through the canvas, even though the canvas visually sits on top in z-index.

High-DPI without the blur

resize() reads window.devicePixelRatio (capped at 2 to avoid oversized backing stores on very high-density phones), sizes the canvas's backing store to clientWidth * DPR, and then calls ctx.setTransform(DPR, 0, 0, DPR, 0, 0) so every subsequent drawing call can keep using plain CSS-pixel coordinates. Skip that step and the snow renders soft and slightly blurred on Retina displays — a common canvas mistake that's invisible until you compare it side by side with a native image.

Why each flake needs its own phase

Every flake stores a drift speed and a driftPhase — a random starting angle for its personal sine wave. The horizontal sway comes from Math.sin(t * f.drift + f.driftPhase) * 0.6, so no two flakes swing left and right in unison. That single random phase offset is the entire trick behind the effect looking organic instead of mechanical, the same idea used in the layered waves of aurora background and the particle jitter of starfield.

Recycling instead of removing

Rather than deleting flakes that fall past the bottom edge and spawning new ones (which would need array splicing every frame), a flake that exits is simply reassigned fresh random properties and reset to y: -10 via makeFlake(false). The array length and each element's memory address never change, which keeps the loop allocation-free and steady at any flake count.

Respecting reduced motion

window.matchMedia('(prefers-reduced-motion: reduce)') is checked once on load. If the user has that OS preference set, the loop never starts — instead, one static frame of snow is drawn so the section still looks dressed for winter without any perpetual animation running in the background. Pair this overlay with a particle network hero for tech-brand pages, or dial down the flake count and speed for a subtle year-round texture layer.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA hero section renders with snow drifting over its content.
  2. 2
    Watch the driftEach flake sways on its own sine phase, not in lockstep.
  3. 3
    Click the buttonpointer-events: none means clicks pass straight through the canvas.
  4. 4
    Resize the windowThe canvas rescales for devicePixelRatio and stays crisp.
  5. 5
    Enable reduced motion in your OSReload — the loop is skipped and one static frame draws instead.
  6. 6
    Tune the stormChange FLAKE_COUNT, speed range, and drift amplitude.

Real-world uses

Common Use Cases

Seasonal sale banners
Layer winter snow over a holiday promotion, with pointer-events: none keeping every button beneath fully clickable.
Subtle header ambience
Add a texture layer to a header all year round, with each flake following its own sine drift phase instead of swaying in sync.
Starfield alternatives
Swap for a starfield background off season, since both use the same click-through transparent overlay approach.
E-commerce campaign promos
Layer over a hero promotion without blocking clicks, recycling flakes in place instead of splicing arrays so no garbage is created.
Event and conference pages
Set a wintry mood for a seasonal gathering, with devicePixelRatio scaling keeping flakes crisp on retina screens.
Related: Corner Peel Hover Reveal Card
See the Corner Peel Hover Reveal Card for a related animations pattern worth pairing with this one.
Related: Vanilla SVG Icon Morph (No Library)
See the Vanilla SVG Icon Morph (No Library) for a related animations pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

The canvas is positioned absolutely over the entire hero section so it can draw snow above the text and button, but without pointer-events: none it would also intercept every click and hover meant for that content. Setting it to none makes the canvas purely visual — the browser routes all pointer events straight through to whatever is beneath it.

Each flake gets its own random size, fall speed, opacity, and — most importantly — its own drift phase, a random starting angle fed into a per-flake sine wave that controls horizontal sway. Because no two flakes share a phase, they never sway in unison, so the aggregate motion reads as organic wind rather than a looping sprite sheet.

Yes. On load it checks window.matchMedia for the prefers-reduced-motion: reduce media feature. If the user has that OS-level accessibility setting enabled, the requestAnimationFrame loop never starts, and a single static frame of snow is drawn instead — so the section still looks seasonally appropriate without a perpetual animation running for users who have asked for less motion.

Without it, the canvas backing store matches CSS pixels 1:1, and on a high-density screen the browser has to upscale the rendered result, producing visibly soft or blurry snowflakes. Sizing the backing store to clientWidth times the device pixel ratio and then calling ctx.setTransform(DPR, 0, 0, DPR, 0, 0) lets every drawing call keep using simple CSS-pixel coordinates while still rendering at full native resolution.

Move the setup into a mount effect: create the canvas ref, run resize() and seed() once mounted, start the requestAnimationFrame loop, and store the frame id so you can cancelAnimationFrame it on unmount. Re-attach the resize listener in the same effect and remove it in cleanup. The HTML and CSS structure — an absolutely positioned canvas inside a relative wrapper — ports unchanged.