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

@npmring/particle-wave

v1.5.0

Published

Interactive, physics-driven particle cloud engine for the browser. Loads a .pwcloud point cloud and renders it on a canvas with spring physics, hover forces, and click-driven wave fronts. Zero runtime dependencies.

Readme

@npmring/particle-wave

Interactive, physics-driven particle cloud for the browser. Loads a .pwcloud point cloud, holds every particle on a spring at its rest position, and lets the cursor and clicks disturb it. Canvas 2-D, ES module, zero runtime dependencies.

Point clouds are produced by the companion Python CLI, particle-wave, which turns any image into a weighted set of points.

Install

npm install @npmring/particle-wave

Use

import ParticleWave from '@npmring/particle-wave';
import '@npmring/particle-wave/style.css';

const pw = await ParticleWave.init(document.getElementById('canvas'), {
  src: '/assets/logo.pwcloud',
  particleColor: '#7b93ff',
  mouseMode: 'repel',
  leftClickMode: 'outward_wave',
  rightClickMode: 'inward_wave',
});

pw.setMode('attract');
pw.triggerWave({ x: 300, y: 200 });
pw.setConfig({ waveStrength: 200 });
pw.pause();
pw.resume();
pw.destroy();

The canvas needs a CSS size; the engine sets the backing store itself and keeps it in step with a ResizeObserver.

Changing shape

morphTo moves the cloud into another shape instead of replacing it. Each particle is paired with a point of the new cloud along a shared space-filling curve, so neighbours stay neighbours and the field deforms rather than scrambles; the spring the particles are already under chases the result, and the lag of that chase is what makes it look like the cloud is being pulled.

const pw = await ParticleWave.init(canvas, {
  src: '/clouds/logo.pwcloud',
  capacity: 8000,          // the largest cloud this instance will ever show
});

await pw.morphTo('/clouds/orrery.pwcloud', { duration: 1400, stagger: 0.4 });

Point counts do not have to match. A denser cloud fills spare capacity and is thinned along the curve if there is none; a sparser one lets the surplus dissolve in flight. Weight, radius, alpha and per-particle source colour travel with position.

Set capacity at init if you intend to morph. Without it the buffers are sized to the first cloud and a denser one is thinned to fit, which still works and still looks right, only at the first cloud's density.

Calling morphTo again mid-transition takes over from wherever the field has got to, so a visitor clicking quickly between subjects sees one continuous motion instead of a queue. The promise resolves when the field lands; pw:morphstart and pw:morphend fire on the canvas, and pw.isMorphing reports the state.

Interaction model

Two distinct things happen on click, and the difference matters when tuning:

  • A wave is a front that travels outward (or inward) from the click and kicks each particle once as it passes. waveSpeed, waveWidth, waveStrength, rippleCount.
  • A burst is a stationary radial field that holds everything inside its radius for as long as the button is down. burstRadiusScale, burstStopRadius, burstDuration, burstOutwardGain.

Wave fronts are drawn on the canvas as a soft additive band around a crisp core line (clickWaveVisual*). Without that the wave is only visible through the motion it imparts, which reads as the cloud twitching for no reason.

Types

particle-wave.d.ts ships with the package and declares the full config surface. It is hand-maintained rather than generated, because the inferred types are too narrow to be useful at the call site — DEFAULTS.src is null, so TypeScript would infer src as null | undefined and reject the URL string the engine actually requires.

Config schema

engine_fields.json describes every tunable option with its range, unit, default, and help text, grouped for display. Import it to drive a settings panel without restating the schema:

import fields from '@npmring/particle-wave/engine-fields.json' with { type: 'json' };

Documentation

Full API, algorithm, and .pwcloud format specification: https://github.com/CoronRing/ParticleWave

License

MIT