npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zakkster/lite-snow

v1.0.0

Published

Zero-GC, SoA environmental snow engine with drift physics, Z-depth parallax, ellipse-based accumulation, dimension caching, and off-screen culling. Designed for cinematic snowfall with high-performance rendering and natural flake behavior.

Readme

@zakkster/lite-snow

npm version npm bundle size npm downloads npm total downloads TypeScript Dependencies License: MIT

Zero-GC SoA environmental snow engine with drift physics, Z-depth parallax, ellipse accumulation, and bucketed rendering. One dependency. 3 presets. 198 lines.

Live Demo

https://cdpn.io/pen/debug/yyapJqB

Why lite-snow?

| Feature | lite-snow | tsparticles | weatherJS | p5.js | |---|---|---|---|---| | Zero-GC hot path | Yes | No | No | No | | SoA flat arrays | 12 arrays | No | No | No | | Z-depth parallax | Yes (0.2–1.0) | No | No | Manual | | Sinusoidal drift | Per-flake | Partial | No | Manual | | Melt accumulation | Ellipse morph | No | No | No | | Bucketed rendering | 3 tiers | No | No | No | | Built-in presets | 3 | Config-heavy | No | No | | OKLCH color | Yes | No | No | No | | Bundle size | < 2KB | ~40KB | ~10KB | ~800KB |

Installation

npm install @zakkster/lite-snow

Quick Start

import { SnowEngine } from '@zakkster/lite-snow';

const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
const snow = new SnowEngine(10000);

let w = canvas.width, h = canvas.height;
let last = performance.now();

function loop(time) {
    const dt = Math.min((time - last) / 1000, 0.1);
    last = time;

    snow.spawn(dt, w, h);

    ctx.fillStyle = '#0a0a1a';
    ctx.fillRect(0, 0, w, h);         // dark sky background

    snow.updateAndDraw(ctx, dt, w, h); // snow overlays on top
    requestAnimationFrame(loop);
}

requestAnimationFrame(loop);

One-Liner with Presets

import { SnowEngine, SNOW_PRESETS } from '@zakkster/lite-snow';

const blizzard = new SnowEngine(15000, SNOW_PRESETS.blizzard);

Important: updateAndDraw() does not clear the canvas. Snow is an overlay effect. Call ctx.clearRect() or draw your background before calling updateAndDraw().


Presets

| Preset | Density | Wind | Gravity | Drift | Radius | Feel | |---|---|---|---|---|---|---| | SNOW_PRESETS.flurry | 10 | 30 | 40 | 15 | 2.5 | Gentle, peaceful | | SNOW_PRESETS.heavy | 24 | 150 | 80 | 25 | 3.5 | Dense, windy | | SNOW_PRESETS.blizzard | 40 | 400 | 250 | 50 | 2.0 | Extreme whiteout |


The Snow Pipeline

Phase 1: Falling Flake (state = 1)

Snowflakes spawn above the viewport with random Z-depth (0.2–1.0). Every parameter scales by Z:

| Property | Formula | Effect | |---|---|---| | Fall speed | gravity × z | Far flakes fall slower | | Wind drift | wind × z | Far flakes drift less | | Flake radius | (baseRadius ± jitter) × z | Far flakes are smaller | | Drift amplitude | driftAmplitude × z | Far flakes sway less | | Render alpha | z × 0.8 | Far flakes are faint |

Each flake has a unique sinusoidal drift — a sine wave with per-flake random phase, frequency, and amplitude. This produces the natural floating-leaf motion that makes snow look real instead of just falling vertically.

All Z-dependent values are precomputed at spawn: gz[], wz[], radius[], driftAmp[], bucket[].

Phase 2: Melt (state = 2)

When a flake reaches the floor (y >= h), it transitions to a settled state:

  • Shape morphs from a circle to a flat ellipse (2.5× width, 0.5× height) — simulating a flake flattening on the ground
  • Alpha fades from z to 0 over meltTimeMin to meltTimeMax seconds
  • Computed via invMeltMax (one division per frame, not per flake)

This creates a subtle accumulation layer at the bottom of the canvas — flakes don't just disappear, they settle and melt.

Bucketed Rendering

Flakes are binned into 3 depth tiers at spawn:

| Bucket | Z Range | Alpha | Radius Scale | |---|---|---|---| | 0 (far) | 0.2–0.4 | 0.24 | ~0.3× | | 1 (mid) | 0.4–0.7 | 0.44 | ~0.55× | | 2 (near) | 0.7–1.0 | 0.72 | ~0.9× |

Each bucket renders in one batched ctx.fill() call — 3 draw calls for all 10,000 flakes.


Full Config Reference

All config values are live-mutable between frames.

| Option | Type | Default | Description | |---|---|---|---| | gravity | number | 40 | Downward acceleration (px/s²). Snow is very light. | | wind | number | 30 | Horizontal wind (px/s). Positive = right. | | density | number | 10.0 | Spawn multiplier. Auto-scales with canvas area. | | baseRadius | number | 2.5 | Base flake radius (px). Depth-scaled per flake. | | driftAmplitude | number | 15 | Horizontal drift sine amplitude (px). Depth-scaled. | | driftFreq | number | 1.0 | Drift sine frequency (Hz). Per-flake jitter ±0.25. | | meltTimeMin | number | 2.0 | Minimum time before settled flake fades (seconds). | | meltTimeMax | number | 5.0 | Maximum melt time (seconds). | | color | OklchColor | string | 'oklch(0.98 0.02 250)' | Flake color. Pre-parsed at construction. | | rng | Function | Math.random | RNG function. Inject for determinism. |


Canvas Setup (No Built-in Resize)

import { SnowEngine } from '@zakkster/lite-snow';

const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
const snow = new SnowEngine();

let w = 0, h = 0;
const dpr = window.devicePixelRatio || 1;

function updateSize() {
    w = canvas.clientWidth || window.innerWidth;
    h = canvas.clientHeight || window.innerHeight;
    canvas.width = w * dpr;
    canvas.height = h * dpr;
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.scale(dpr, dpr);
}

let scheduled = false;
new ResizeObserver(() => {
    if (!scheduled) {
        scheduled = true;
        requestAnimationFrame(() => { scheduled = false; updateSize(); });
    }
}).observe(canvas.parentElement || document.body);

updateSize();

Seeded Random (Deterministic)

import { SnowEngine } from '@zakkster/lite-snow';
import { Random } from '@zakkster/lite-random';

const rng = new Random(42);
const snow = new SnowEngine(10000, { rng: () => rng.next() });

Recipes

import { SnowEngine, SNOW_PRESETS } from '@zakkster/lite-snow';

const snow = new SnowEngine(8000, {
    ...SNOW_PRESETS.flurry,
    color: { l: 0.95, c: 0.02, h: 240 },
});
const snow = new SnowEngine(12000, SNOW_PRESETS.heavy);
const snow = new SnowEngine(15000, SNOW_PRESETS.blizzard);

// Ramp wind over time
let windTarget = 400;
setInterval(() => {
    windTarget = 200 + Math.random() * 500 * (Math.random() > 0.5 ? 1 : -1);
}, 3000);

// In loop:
snow.config.wind += (windTarget - snow.config.wind) * dt * 1.5;
function gameLoop(dt) {
    snow.spawn(dt, w, h);

    ctx.clearRect(0, 0, w, h);
    drawBackground();
    drawCharacters();
    drawUI();

    snow.updateAndDraw(ctx, dt, w, h);
}
// Volcanic ash
const ash = new SnowEngine(6000, {
    color: { l: 0.3, c: 0.05, h: 30 },
    gravity: 60,
    driftAmplitude: 20,
});

// Cherry blossom petals
const petals = new SnowEngine(4000, {
    color: { l: 0.8, c: 0.15, h: 340 },
    gravity: 25,
    driftAmplitude: 30,
    baseRadius: 3.5,
});

// Floating embers
const embers = new SnowEngine(3000, {
    color: { l: 0.6, c: 0.25, h: 30 },
    gravity: -15,  // float upward!
    wind: 50,
    driftAmplitude: 10,
    baseRadius: 1.5,
});
import { SnowEngine } from '@zakkster/lite-snow';
import { RainEngine } from '@zakkster/lite-rain';
import { FireworksEngine } from '@zakkster/lite-fireworks';

const snow = new SnowEngine(8000);
const rain = new RainEngine(6000, { density: 3 });
const fireworks = new FireworksEngine(5000);

function loop(time) {
    const dt = /* ... */;

    snow.spawn(dt, w, h);
    rain.spawn(dt, w, h);

    fireworks.updateAndDraw(ctx, dt, w, h); // bloom background
    snow.updateAndDraw(ctx, dt, w, h);       // snow overlay
    rain.updateAndDraw(ctx, dt, w, h);       // rain on top
}
windSlider.oninput = () => snow.config.wind = +windSlider.value;
densitySlider.oninput = () => snow.config.density = +densitySlider.value;
gravitySlider.oninput = () => snow.config.gravity = +gravitySlider.value;
driftSlider.oninput = () => snow.config.driftAmplitude = +driftSlider.value;

API

new SnowEngine(maxParticles?, config?)

| Parameter | Type | Default | Description | |---|---|---|---| | maxParticles | number | 10000 | Pool capacity. Shared between flakes and melting. | | config | SnowConfig | see above | All options. Live-mutable. |

Methods

| Method | Description | |---|---| | .spawn(dt, w, h) | Spawn new flakes. Auto-scales with area × density. | | .updateAndDraw(ctx, dt, w, h) | Physics + render. Does not clear canvas. | | .clear() | Kill all particles immediately. | | .destroy() | Null all 12 typed arrays. Idempotent. |

SNOW_PRESETS

| Preset | Description | |---|---| | .flurry | Gentle snowfall | | .heavy | Dense, windy | | .blizzard | Extreme whiteout |


License

MIT

Part of the @zakkster ecosystem

Zero-GC, deterministic, tree-shakeable micro-libraries for high-performance web presentation.