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-sparks

v1.0.0

Published

Zero-GC, SoA spark and debris engine featuring vector velocity stretching, floor restitution, thermodynamic OKLCH heat gradients, and hyper-optimized ballistic physics. Ideal for impacts, welding, grinding, fireworks trails, and high‑energy VFX.

Readme

@zakkster/lite-sparks

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

Zero-GC SoA spark and debris engine. Velocity stretching, floor bounce physics, thermodynamic OKLCH heat gradient. One dependency. 149 lines.

→ Live Interactive Playground

Why lite-sparks?

| Feature | lite-sparks | tsparticles | fireworks-js | p5.js | |---|---|---|---|---| | Zero-GC hot path | Yes | No | No | No | | SoA flat arrays | Yes | No | No | No | | Floor bounce | Yes (restitution) | No | No | Manual | | Velocity stretching | Yes | No | No | Manual | | Heat gradient | OKLCH thermodynamic | RGB only | CSS only | Manual | | Sleep state | Yes | No | No | No | | Seeded RNG (optional) | Yes | No | No | No | | Dependencies | 1 | 10+ | 0 | 0 | | Bundle size | < 2KB | ~40KB | ~4KB | ~800KB |

Installation

npm install @zakkster/lite-sparks

Quick Start

import { SparkEngine } from '@zakkster/lite-sparks';

const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
const sparks = new SparkEngine(5000);

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;
    sparks.updateAndDraw(ctx, dt, w, h);
    requestAnimationFrame(loop);
}

// Burst: 50 sparks, upward cone, 200–800 px/s
canvas.addEventListener('click', (e) => {
    sparks.burst(e.clientX, e.clientY, 50, -Math.PI, 0, 200, 800);
});

requestAnimationFrame(loop);

Spark Physics

Thermodynamic Heat Gradient

Every spark starts white-hot and cools to cherry red as it dies. The default 4-stop gradient maps remaining life ratio to color:

| Life Ratio | Color | Temperature | |---|---|---| | 1.0 (just born) | oklch(0.98, 0.05, 90) | White-hot core | | 0.7 | oklch(0.85, 0.20, 70) | Yellow | | 0.4 | oklch(0.60, 0.25, 30) | Orange | | 0.0 (dying) | oklch(0.30, 0.20, 20) | Cherry red |

The color index uses a precomputed invLife[] multiplier to avoid division in the render loop.

Velocity Stretching

Each spark renders as a line from its current position to a tail point computed from velocity:

tailX = x - vx × stretch
tailY = y - vy × stretch

Fast sparks draw long lines. Slow sparks draw dots. The stretch config controls tail length. Higher stretch = longer comet trails.

Floor Bounce (Restitution)

When a spark hits the floor (y >= h), it bounces:

vy *= -restitution    (reverse + energy loss)
vx *= floorFriction   (horizontal slowdown on contact)

When bounce velocity drops below 20 px/s, the spark stops bouncing and rests. When both axes drop below threshold, the spark enters sleep state — physics are completely bypassed, saving CPU for particles that have settled.

Weight

Each spark gets a random weight (1.0–4.0) that controls lineWidth. Heavier sparks render as thicker strokes. Weight also offsets the floor position slightly so sparks don't all pile at the exact same Y.


Full Config Reference

All config values are live-mutable between frames.

| Option | Type | Default | Description | |---|---|---|---| | gravity | number | 800 | Downward acceleration in px/s². Sparks are heavy — higher than fireworks. | | friction | number | 0.99 | Air friction per frame (0–1). 0.99 = very light drag for fast sparks. | | floorFriction | number | 0.85 | Horizontal friction on floor bounce. Lower = sparks skid further. | | restitution | number | 0.4 | Bounce energy retention (0 = no bounce, 1 = perfect elastic). | | stretch | number | 0.04 | Velocity tail multiplier. Higher = longer comet trails. | | transparentBackground | boolean | false | false = additive blending (dark bg). true = source-over (light bg). | | heatColors | Array | 4 OKLCH stops | Heat gradient. Index 0 = coldest (dying), last = hottest (born). Pre-parsed at construction. | | rng | Function | Math.random | RNG function () => number in [0, 1). Inject seeded RNG for determinism. |


Canvas Setup (No Built-in Resize — By Design)

lite-sparks is a pure engine — it doesn't own the canvas. Here's the recommended setup:

import { SparkEngine } from '@zakkster/lite-sparks';

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

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);
}

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

updateSize();

let last = performance.now();
function loop(time) {
    const dt = Math.min((time - last) / 1000, 0.1);
    last = time;
    sparks.updateAndDraw(ctx, dt, w, h);
    requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

Seeded Random (Deterministic Replays)

lite-sparks uses Math.random by default. For deterministic output, inject @zakkster/lite-random:

import { SparkEngine } from '@zakkster/lite-sparks';
import { Random } from '@zakkster/lite-random';

const rng = new Random(42);
const sparks = new SparkEngine(5000, { rng: () => rng.next() });

// Same seed + same burst sequence = identical output
sparks.burst(400, 300, 50, -Math.PI, 0, 200, 800);

// Reset for replay
rng.reset(42);
sparks.clear();
sparks.burst(400, 300, 50, -Math.PI, 0, 200, 800); // exact same sparks

Recipes

Hold mouse down for a constant upward spray of sparks, like a welding torch or angle grinder.

const sparks = new SparkEngine(5000, { gravity: 600, restitution: 0.5, stretch: 0.05 });

let drawing = false, mx = 0, my = 0;
canvas.addEventListener('mousedown', () => drawing = true);
canvas.addEventListener('mouseup', () => drawing = false);
canvas.addEventListener('mousemove', (e) => { mx = e.clientX; my = e.clientY; });

function loop(time) {
    const dt = /* ... */;
    if (drawing) {
        // Wide upward cone, 15 sparks per frame
        sparks.burst(mx, my, 15, Math.PI + 0.5, Math.PI * 2 - 0.5, 200, 800);
    }
    sparks.updateAndDraw(ctx, dt, w, h);
    requestAnimationFrame(loop);
}

One massive radial burst on click — all angles, high speed, short life.

const sparks = new SparkEngine(8000, { gravity: 1200, restitution: 0.3, stretch: 0.03 });

canvas.addEventListener('click', (e) => {
    sparks.burst(e.clientX, e.clientY, 300, 0, Math.PI * 2, 400, 1500, 0.3, 1.2);
});

Slow, heavy particles dripping downward with long life and low bounce.

const sparks = new SparkEngine(3000, {
    gravity: 400,
    restitution: 0.1,
    stretch: 0.08,
    friction: 0.995,
});

// Drip downward only (0 to π = below horizon)
setInterval(() => {
    sparks.burst(w / 2, 100, 5, 0, Math.PI, 10, 150, 1.0, 3.0);
}, 100);

Replace the default gradient with brand colors or fantasy metals.

const sparks = new SparkEngine(5000, {
    heatColors: [
        { l: 0.2, c: 0.15, h: 270 },  // cold: deep purple
        { l: 0.5, c: 0.25, h: 300 },  // warm: magenta
        { l: 0.7, c: 0.20, h: 330 },  // hot: pink
        { l: 0.95, c: 0.05, h: 0 },   // white-hot: near white
    ],
});

On light backgrounds, disable additive blending so sparks render with proper opacity.

const sparks = new SparkEngine(5000, {
    transparentBackground: true,
});

// Toggle at runtime
modeToggle.onchange = () => {
    sparks.config.transparentBackground = modeToggle.checked;
};

All config values are live-mutable — perfect for debug panels.

gravitySlider.oninput = () => sparks.config.gravity = +gravitySlider.value;
restitutionSlider.oninput = () => sparks.config.restitution = +restitutionSlider.value;
stretchSlider.oninput = () => sparks.config.stretch = +stretchSlider.value;

API

new SparkEngine(maxParticles?, config?)

Creates a spark engine with a pre-allocated SoA particle pool.

| Parameter | Type | Default | Description | |---|---|---|---| | maxParticles | number | 5000 | Pool capacity. Ring buffer behavior. | | config | SparkConfig | see above | All options. Live-mutable after construction. |

Methods

| Method | Description | |---|---| | .burst(x, y, count, angleMin, angleMax, speedMin, speedMax, lifeMin?, lifeMax?) | Spawn sparks in an angular cone. Angles in radians. | | .updateAndDraw(ctx, dt, w, h) | Physics + render. dt in seconds. h is floor Y. | | .clear() | Kill all particles immediately. | | .destroy() | Null all typed arrays. Idempotent. |

burst() Angle Guide

          -π/2 (straight up)
            │
   -π ──────┼────── 0 (right)
   (left)   │
          π/2 (straight down)

Common cones:

  • Upward fan: angleMin = -Math.PI, angleMax = 0
  • Full radial: angleMin = 0, angleMax = Math.PI * 2
  • Downward drip: angleMin = 0, angleMax = Math.PI
  • Right spray: angleMin = -0.5, angleMax = 0.5

License

MIT

Part of the @zakkster ecosystem

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