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

hz-particles

v1.0.15

Published

High-performance WebGPU particle engine with compute shaders, GPU instancing, GLB model support, and skeletal animations

Readme

hz-particles

npm version license WebGPU

A high-performance WebGPU particle engine for the web. Use it as a standalone WebGPU renderer or as a plug-and-play React Three Fiber component.

Features

  • WebGPU Compute Shaders — GPU-accelerated particle physics simulation
  • GPU Instancing — Efficient rendering of thousands of particles
  • GLB Model Support — Use 3D models as particle shapes with automatic texture extraction
  • Skeletal Animations — Animated GLB models with full animation control
  • Scene Serialization — Save/load particle configurations as JSON
  • Multiple Emitter Shapes — Sphere, cube, cylinder, circle, square emission patterns
  • Real-time Physics — Gravity, attractors, drag, velocity, and lifetime management
  • React Three Fiber Component — Drop-in HZParticlesFX component with preset support
  • 3D Object Support — Static 3D objects alongside particle systems

Requirements

WebGPU-compatible browser required:

  • Chrome/Edge 113+ (stable)
  • Firefox Nightly (experimental)
  • Safari Technology Preview (experimental)

Check browser support: caniuse.com/webgpu

Installation

WebGPU

npm install hz-particles
import { initWebGPU, ParticleSystemManager } from 'hz-particles';

React Three Fiber

npm install hz-particles
import { HZParticlesFX } from 'hz-particles/r3f';

Important: Your R3F Canvas must use WebGPURenderer. Pass gl={{ powerPreference: 'high-performance' }} and configure Three.js to use its WebGPU backend, or use @react-three/fiber with a WebGPU-capable renderer setup.

Quick Start

WebGPU

import { initWebGPU, ParticleSystemManager } from 'hz-particles';

// 1. Setup WebGPU context
const canvas = document.getElementById('webgpu-canvas');
canvas.width = 800;
canvas.height = 600;
const { device, context, format } = await initWebGPU(canvas);

// 2. Create particle system manager
const manager = new ParticleSystemManager(device);

// 3. Create a particle system
const systemId = manager.createParticleSystem({
  maxParticles: 10000,
  particleCount: 1000,
});

// 4. Initialize compute pipeline
const system = manager.getActiveSystem();
await system.initComputePipeline(device);

// 5. Render loop
let lastTime = performance.now();

function render() {
  const currentTime = performance.now();
  const deltaTime = (currentTime - lastTime) / 1000;
  lastTime = currentTime;

  manager.updateAllSystems(deltaTime);

  const commandEncoder = device.createCommandEncoder();
  const renderPass = commandEncoder.beginRenderPass({
    colorAttachments: [{
      view: context.getCurrentTexture().createView(),
      clearValue: { r: 0, g: 0, b: 0, a: 1 },
      loadOp: 'clear',
      storeOp: 'store',
    }],
  });

  system.render(renderPass);
  renderPass.end();
  device.queue.submit([commandEncoder.finish()]);

  requestAnimationFrame(render);
}

render();

React Three Fiber

Minimal example with a preset file:

import { HZParticlesFX } from 'hz-particles/r3f';
import explosionPreset from './presets/explosion.json';

function Scene() {
  return (
    <HZParticlesFX
      preset={explosionPreset}
      position={[0, 1, 0]}
    />
  );
}

Complete example with all key props:

import { useRef, useState } from 'react';
import { HZParticlesFX } from 'hz-particles/r3f';
import explosionPreset from './presets/explosion.json';

function Scene() {
  const targetRef = useRef();
  const [resetKey, setResetKey] = useState(0);

  return (
    <>
      <mesh ref={targetRef} position={[0, 1, 0]}>
        <boxGeometry />
        <meshStandardMaterial />
      </mesh>

      <HZParticlesFX
        preset={explosionPreset}
        positionRef={targetRef}
        scale={1.5}
        resetKey={resetKey}
        onComplete={() => console.log('effect finished')}
      />

      <button onClick={() => setResetKey(k => k + 1)}>
        Replay
      </button>
    </>
  );
}

Props Reference

The HZParticlesFX component accepts the following props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | preset | SceneData \| string | — | Particle preset — pass a JSON object or a URL/path to a JSON file | | position | [number, number, number] | [0, 0, 0] | Static world-space position of the effect | | positionRef | React.RefObject<Object3D> | — | Attach the effect to a scene object; position tracks the ref each frame | | autoPlay | boolean | true | Start playing immediately when mounted | | visible | boolean | true | Show or hide the effect without unmounting | | scale | number | 1 | Uniform scale applied to the entire effect | | resetKey | number | 0 | Increment to reset and respawn the particle system | | onComplete | () => void | — | Callback fired when the effect finishes playing |

Preset Configuration

Presets are plain JSON files that describe a particle scene. Key fields:

| Field | Type | Description | |-------|------|-------------| | particleCount | number | Number of active particles | | lifetime | number | Particle lifetime in seconds | | emissionRate | number | Particles emitted per second | | emissionShape | string | Emitter shape: sphere, cube, cylinder, circle, square | | particleSize | number | Base size of each particle | | colors | string[] | Array of hex color values interpolated over lifetime | | fadeIn | number | Opacity fade-in duration (0–1, fraction of lifetime) | | fadeOut | number | Opacity fade-out start (0–1, fraction of lifetime) | | bloom | boolean | Enable bloom glow on particles | | gravity | number | Gravity strength applied to particles | | damping | number | Velocity damping factor (0 = no drag, 1 = full stop) | | emissionTrailEnabled | boolean | Enable particle trail rendering | | emissionTrailDuration | number | How long trail segments persist in seconds | | emissionTrailWidth | number | Width of the emission trail |

API Reference

initWebGPU(canvas)

Convenience helper to initialize a WebGPU context and device.

async function initWebGPU(canvas: HTMLCanvasElement): Promise<{
  device: GPUDevice,
  context: GPUCanvasContext,
  format: GPUTextureFormat,
  canvas: HTMLCanvasElement
}>

Parameters:

  • canvas (required) — Canvas element. Set canvas.width and canvas.height before calling.

Returns: Object with WebGPU device, canvas context, texture format, and canvas element.

Throws: Error if canvas is missing or WebGPU is not supported.


ParticleSystem

Core particle system class managing simulation and rendering.

class ParticleSystem {
  constructor(device: GPUDevice, config?: object)
}

Config options:

  • maxParticles (number, default 10000) — Maximum particle buffer size
  • particleCount (number, default 100) — Initial active particle count

Key Methods:

| Method | Description | |--------|-------------| | initComputePipeline(device) | Initialize GPU compute and render pipelines. Must be called before rendering. | | setTexture(imageBitmap) | Set particle texture from an ImageBitmap. | | setGLBModel(arrayBuffer) | Use GLB model geometry as particle shape. Automatically extracts textures. | | updateParticles(deltaTime) | Execute a physics simulation step on the GPU. | | spawnParticles() | Emit new particles according to emitter configuration. | | setGravity(value) | Set gravity strength (default 9.8). | | setAttractor(strength, position) | Set an attractor point with strength and 3D position [x, y, z]. | | render(renderPass) | Render particles to the current render pass. |


ParticleSystemManager

Manages multiple particle systems within a single scene.

class ParticleSystemManager {
  constructor(device: GPUDevice)
}

Key Methods:

| Method | Description | |--------|-------------| | createParticleSystem(config?) | Create a new particle system. Returns system ID. | | getActiveSystem() | Get the currently active ParticleSystem instance. | | getActiveConfig() | Get the active system configuration object. | | setActiveSystem(index) | Switch active system by index. Returns success status. | | removeSystem(index) | Remove a system by index. Returns success status. | | updateAllSystems(deltaTime) | Update physics for all systems. | | getSystemsList() | Get list of all systems with name, id, index, isActive. | | duplicateActiveSystem() | Clone the active system. Returns new system ID. | | replaceSystems(sceneData) | Load a scene from serialized data. Returns success status. |


parseGLB(arrayBuffer)

Parse GLB binary format and extract geometry data.

async function parseGLB(arrayBuffer: ArrayBuffer): Promise<{
  positions: Float32Array,
  normals: Float32Array,
  indices: Uint16Array | Uint32Array,
  texCoords: Float32Array | null,
  vertexCount: number,
  indexCount: number,
  animationData: object | null,
  hasBaseColorTexture: boolean
}>

GLBAnimator

Handles skeletal animation playback for animated GLB models.

class GLBAnimator {
  constructor(animationData: object)

  currentTime: number
  playing: boolean
  speed: number   // default 1.0
  loop: boolean   // default true
}

Key Methods:

| Method | Description | |--------|-------------| | setRestPose(positions, normals) | Set the T-pose/bind pose for animation. | | update(deltaTime) | Advance animation. Returns { positions, normals, changed }. | | setAnimation(index) | Switch to animation clip by index. | | getAnimationNames() | Get list of available animation clip names. |

GLB Models & Animation

import { parseGLB, GLBAnimator } from 'hz-particles';

// Load GLB file
const response = await fetch('model.glb');
const arrayBuffer = await response.arrayBuffer();
const glbData = await parseGLB(arrayBuffer);

// Use as particle shape
await system.setGLBModel(arrayBuffer);

// Setup animation (if model has animations)
if (glbData.animationData) {
  const animator = new GLBAnimator(glbData.animationData);
  animator.setRestPose(glbData.positions, glbData.normals);
  animator.playing = true;
  animator.loop = true;
  animator.speed = 1.0;

  // In render loop:
  const { positions, normals, changed } = animator.update(deltaTime);
  if (changed) {
    // Update particle system with new geometry
    // (See full API documentation for geometry update methods)
  }
}

Scene Save/Load

import { saveScene, loadScene } from 'hz-particles';

// Save current scene
document.getElementById('save-button').addEventListener('click', () => {
  saveScene(manager);  // Downloads scene.json
});

// Load scene from file input
document.getElementById('file-input').addEventListener('change', async (event) => {
  const success = await loadScene(event, manager);
  if (success) {
    console.log('Scene loaded successfully');
  }
});

TypeScript

Type declarations are not yet included in this package. For TypeScript projects, suppress the import error with:

// @ts-ignore
import { ParticleSystem, initWebGPU } from 'hz-particles';
// @ts-ignore
import { HZParticlesFX } from 'hz-particles/r3f';

Community-contributed type definitions are welcome via PR.

Secondary Exports

The following modules are also exported for advanced usage:

  • ParticleEmitter — Emission shape configuration (sphere, cube, cylinder, circle, square)
  • ParticlePhysics — Physics simulation parameter management
  • ParticleTextureManager — Texture loading utilities
  • Objects3DManager — 3D object scene management
  • saveScene(manager) — Export scene as JSON file download
  • loadScene(event) — Load scene from file input event
  • extractGLBTexture(arrayBuffer) — Extract base color texture from GLB file
  • Shader exports — WGSL shader code for compute and rendering pipelines
  • Geometry helpers — Primitive shape generators (cube, sphere, etc.)
  • Render pipeline creators — Low-level WebGPU pipeline construction

Online Editor

Try the interactive particle editor at http://localhost:8110/editor when running the Docker container (docker-compose up from the repository root).

The editor provides a visual interface for:

  • Real-time particle system configuration
  • Emitter shape selection and tuning
  • GLB model import and animation control
  • Scene preset library
  • Export/import scene JSON

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see LICENSE file for details.

Copyright (c) 2025 HZ