Snow Fall
One hundred white snowflakes fall on a 2D canvas — random sizes, speeds and opacity, sinusoidal horizontal sway, continuous RAF loop.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →
3 usage examples



How it works
setupCanvas() reads the parent element's bounding box and resizes the canvas accordingly. At startup, 100 particles are created with independent random values: position (x, y) spread across the full surface, radius r between 1 and 4 px, vertical fall speed speed between 0.5 and 1.5 px/frame, constant horizontal drift wind within ±0.25 px/frame, and opacity between 0.3 and 0.8. These values do not change during a flake's lifetime.
The requestAnimationFrame loop clears the canvas and repaints the #0a0a0f background on every frame. For each flake: y += speed (vertical fall) and x += wind + 0.3 × sin(0.01 × y) — the sinusoidal sway is tied to the Y position, not to time: two flakes at the same height will sway in phase. Flakes are solid arc() calls drawn as rgba(255,255,255, opacity).
Edge wrapping is simple and non-interpolated: a flake that exits at the bottom reappears at the top (y = −5) at a random horizontal position; flakes crossing the left or right edge jump to the opposite side. The pool of 100 flakes is constant — no particle is ever created or destroyed mid-loop.
The effect includes no IntersectionObserver (the RAF runs even when off-screen), no ResizeObserver (the canvas is not recalibrated if the container resizes), and no public API. All constants are hardcoded inside the IIFE.
Accessibility
- prefers-reduced-motion not implemented: the code does not check the OS preference — the animation always runs. To fix: wrap the RAF loop in a
window.matchMedia('(prefers-reduced-motion:reduce)').matchesguard. - The canvas has no
aria-hiddenin the source HTML — addaria-hidden="true"to prevent screen readers from attempting to explore a non-textual canvas. - No
role="img"oraria-labelon the container: if the effect is decorative, hide the canvas from assistive tech; if informative, add a descriptive label on the parent element. - White flakes
rgba(255,255,255,…)on a#0a0a0f(near-black) background yield a contrast ratio > 18:1 — no visual readability issue for the flakes themselves. - The effect is purely decorative and exposes no keyboard or mouse interaction — no native focus management required.
Browser compatibility
Requires Canvas 2D Context and requestAnimationFrame — supported in all modern browsers since 2013.
If <code>getElementById('psSnow')</code> returns <code>null</code> (element missing or ID changed), <code>setupCanvas()</code> returns <code>null</code> and the IIFE exits silently — no JS error, the container renders with its CSS background.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="demo-preview">
<!-- Positioned wrapper — set width/height to match your container -->
<div class="ps-canvas-wrap" role="img" aria-label="Animated falling snowflakes">
<canvas class="ps-canvas" id="psSnow" aria-hidden="true"></canvas>
</div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| count (JS: `a < 100`) | 100 | Number of active flakes. 60 for a light background, 150 for a dense snowstorm. Above 200, monitor performance on low-end mobile. |
| radius (JS: `3 * Math.random() + 1`) | 1–4 px | Flake radius range. Change both constants: `1.5 * Math.random() + 0.5` for finer flakes; `5 * Math.random() + 2` for larger ones. |
| speed (JS: `1 * Math.random() + .5`) | 0.5–1.5 px/frame | Vertical fall speed per frame. `0.3 * Math.random() + 0.2` for a slow drizzle; `2 * Math.random() + 1` for a heavy storm. |
| wind (JS: `.5 * Math.random() - .25`) | ±0.25 px/frame | Constant horizontal drift per flake. `1 * Math.random() - 0.5` for a visible side wind; `0` for a purely vertical fall. |
| opacity (JS: `.5 * Math.random() + .3`) | 0.3–0.8 | Flake opacity range. `0.15 * Math.random() + 0.05` for near-transparent flakes (frost-on-glass effect). |
| sway amplitude (JS: `0.3 * Math.sin`) | 0.3 | Amplitude of the sinusoidal horizontal sway. 0 = straight vertical fall; 0.8 = turbulent blizzard swirl. |
| sway frequency (JS: `0.01 * n.y`) | 0.01 | Frequency of the sine cycle as a function of Y. 0.03 shortens the cycle (rapid flutter); 0.005 lengthens it (slow, wide undulation). |
FAQ
#0a0a0f each frame via ctx.fillRect(0, 0, w, h). For transparent snow over a custom background, remove the two lines fillStyle = '#0a0a0f' and fillRect(...) from the JS, set the canvas to position: absolute; background: transparent, and place your background in CSS behind the canvas.IntersectionObserver to pause it when the canvas scrolls out of view.rgba(255,255,255, opacity). Replace 255,255,255 with the desired RGB values — for example 173,216,230 for ice-blue or 255,220,180 for cherry blossom petals. For multiple colors, define a color array and pick one randomly when each flake is initialized.