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 🙏

© 2024 – Pkg Stats / Ryan Hefner

particlesystems

v1.0.5

Published

Simple, unopinionated particle system library for javascript / typescript

Downloads

72

Readme

Particlesystems.js is a unopinionated, slim library for creating particle effects. It helps you by taking care of all the tedious bits of particle effects, leaving you with more time to focus on what makes your effects special, the drawing.

Demos

Usage

Configure you particlesystem by providing options for how your system, emitter and particles should behave. Provid a callback that does the actual drawing using your favorite drawing api. Congratulations, you're done!

Basic example

function draw(ps) {
  const ctx = document.querySelector("canvas").getContext("2d");
  for(const p of ps.particles) {
    ctx.globalAlpha = 1 - p.normalizedAge;
    ctx.fillRect(p.position.x, p.position.y, 5, 5);
  });
}

const particlesystem = new ParticleSystem({ 
  emitter: {
    particlesPerSecond: 40,
    particles: {
      initialPos: { x: { min: 100, max: 200 }, y: 0 },
      initialVelocity: { x: 0, y: { min: 10, max: 100 } },
      lifetime: 4
    }
  }
}, draw);
particlesystem.start(); 

Vectors

Internally, all vectors - every position, velocity and force (see below) - are stored with three dimensions. But if you're only using 2d you can usually omit the z-value when providing vector values, it will default to 0 and won't affect any calculations.

const pos1 = { x: 0, y: 10, z: 20 }   //valid - fully defined 3d vector
const pos2 = { x: 0, y: 10 }   //valid - fully defined 2d vector
const pos3 = { y: 30, z: 20 }   //invalid - both x and y are mandatory.

Creating randomness

A particlesystem where all particles start at the exact same place, fly of in the exact same direction with the same speed and live for the exact same duration won't be that interesting. That's why when you define a vector there are some alternatives to just providing constants.

  1. The NumRange-object. Instead of constants for x, y, and z you can provide objects with a min and a max property. If you do this, each time a new vector is created based on your definition, a random number from within the range is assigned to the relevant property.
  2. The factory function. For complete control, instead of providing a vector object, provide a function returning a vector object. Then this function will be invoked everytime a new vector is needed. These factory functions receive what information is available about the new particle. For instance, a factory function for a particle's initial position receives the age of the emitter, but a function for a particle's initial velocity also receives the particles position.

Example: simple factory function

function createOnCircle() {
  const Radius = 10;
  const angle = Math.random()*Math.PI*2;
  return { x: Math.cos(angle)*Radius, y: Math.sin(angle)*Radius }
}

const ps = new ParticleSystem({ 
  emitter: {
    particlesPerSecond: 40,
    particles: {
      initialPos: createOnCircle,
      initialVelocity: { x: 0, y: { min: 10, max: 100 } },
      lifetime: 4
    }
  }
}, draw); //assumes a draw function is defined
pd.start();

Example: factory function generator

function createCircleFactory(radius) {
  return function() {
    const angle = Math.random()*Math.PI*2;
    return { x: Math.cos(angle)*radius, y: Math.sin(angle)*radius }
  }
}

const ps = new ParticleSystem({ 
  emitter: {
    particlesPerSecond: 40,
    particles: {
      initialPos: createCircleFactory(10),
      initialVelocity: { x: 0, y: { min: 10, max: 100 } },
      lifetime: 4
    }
  }
}, draw); //assumes a draw function is defined
ps.start();

Custom data

Sometimes you need to keep track of more parameters than the position and velocity for each particle. You can do this by providing a factory function that return a custom data structure containing all the parameters you need. This factory function gets passed the particle object, if you want your custom data to be initiated based on some standard particle property.

Example: custom data for opacity and scale

function draw(ps) {
  const ctx = document.querySelector("canvas").getContext("2d");
  for(const p of ps.particles) {
    ctx.globalAlpha = p.data.opacity;
    ctx.fillRect(p.position.x, p.position.y, 5*p.data.scale, 5*p.data.scale);
  });
}

const ps = new ParticleSystem({ 
  emitter: {
    particlesPerSecond: 40,
    particles: {
      initialPos: { x: { min: 100, max: 200 }, y: 200 },
      initialVelocity: { x: { min: -20, max: 20 }, y: { min: -10, max: -100 } },
      lifetime: 6,
      customDataFactory: () => ({ 
        opacity: .5 + Math.random()*.5,
        scale: .2 + Math.random()*1.8
      })
    }
  }
}, draw);
ps.start();

Forces - adding some dynamics

To add even more life to your particle systems you can declare forces to act on the particles, basically providing acceleration. This is not very sophisticated yet, and you can only provide forces as constant vectors.

Example: wind and gravity

const ps = new ParticleSystem({ 
  forces: {
    gravity: {x: 0, y: 400, z: 0 },
    wind: { x: -80, y: 0, z: 0 }
  },
  emitter: {
    particlesPerSecond: 40,
    particles: {
      initialPos: { x: { min: 100, max: 200 }, y: 200 },
      initialVelocity: { x: { min: -20, max: 20 }, y: { min: -10, max: -100 } },
      lifetime: 6
    }
  }
}, draw); //assumes a draw function is defined
ps.start();

Spawn on DOM element

There are a lot of use cases for spawning particle systems tied to certain DOM elements. As feedback to user interaction, a nicer hover effect or maybe to just provide some ambient background animation to a certain section of a document.

In order to make these tasks easier, a helper function - SpawnOnDOMElement - is provided. It takes care of all things related to dynamically creating and positioning canvases in response to events, and of course, creating, starting and stopping the spawned particle systems.

Example: particle system as hover effect

function draw(canvas, ps) {
  const ctx = canvas.getContext("2d");
  const Radius = 15;
  ctx.fillStyle = 'yellow';
  for(const p of ps.particles) {
    let a = 1 - p.normalizedAge;
    ctx.globalAlpha = -a*a*(a-1)*3; //a nice smooth animation of the alpha that starts and stops at 0 and peaks at about .4
    ctx.beginPath();
    ctx.ellipse(p.position.x, p.position.y, Radius, Radius, 0, 0, 2*Math.PI);
    ctx.fill();
  });
}

SpawnOnDOMElement({  //the SpawnOnDOMElements options
  event: "mouseenter", 
  elements: document.getElementById("btn-test")!,
  stopEvent: "mouseleave")
}, {  //the particlesystem options. Note: no need to provide information about spawned particles position. That's taken care of by SpawnOnDOMElement and made sure to match the provided DOM element
  initialCount: 2,
  emitter: {
    particlesPerSecond: 1.2,
    particles: {
      initialVelocity: MathUtils.Factories.inDirectionOf({x: 8, y: 8}, Math.PI*2, .6),
      lifetime: { min: 2, max: 3 },
    }
  }
}, draw);

API Reference

The ParticleSystem class

Methods

| Name | Remarks | | --- | --- | |constructor(options, drawCallback)|Creates and initiates the particle system based on the provided options. The draw callback gets invoked with a reference to the particlesystem object and a time delta specifying how much time has elapsed since the last draw call (in seconds). | |start()|Starts the particle system| |stop()|Stops the particle system| |addEventListener(type, handler, options)| adds a handler to a specific event| |on(type, handler, options)| alias for addEventListener| |removeEventListener(type, handler, options)| removes a handler for a specific event| |off(type, handler, options)| alias for removeEventListener|

Properties

| Name | Remark | | --- | --- | |particles|Array of all active particles. Particles are automatically removed from this array when their age exceeds their lifetime.|

** Events **

| Name | Remark | | --- | --- | |stop|This event is dispatched when a particle system is stopped. Either explicitly by calling the stop method, or implicitly when the emitter and all particles have died.

The Particle objects

Properties

| Name | Type | Remark | | --- | --- | --- | |position|vector|Current position.| |velocity|vector|Current velocity.| |lifetime|number|The total lifetime of the particle.| |age|number|The current age of the particle.| |normalizedAge|number|The ratio of the current age in relation the total lifetime.| |data| any | User provided custom data for the particle.|

Particlesystem options object

| Property | Default | Remarks | |---|---|---| |initialCount|0|The number of particles when the system starts| |initialAge|optional|Function that gets called for each initial particle to give it an initial age other than 0. This funciton should return a normalized age between 0 and 1. The actual age is automatically calculated using this value and the particles lifetime. |position|{ x: 0, y: 0, z: 0 }|The position of the particle system. Can be used to move the particlesystem over time.| |forces|optional|A dictionary of forces that affect the velocities of particles in the particlesystem.| |emitter| - | An object containing options for the particle emitter. See "particle emitter options" below. |

Particle emitter options object

| Property | Default | Remarks | |---|---|---| |lifetime|optional|If provided, defines for how many seconds the emitter will keep emitting particles. If omitted, particles will be emitted indefinitely.| |particles| - | An object containing options for spawned particles. See "particles options" below. | |particlesPerSecond|40|How many particles to spawn per second. Used by the "random" and the "periodic" spawning strategies (See below).| |sequence|optional|An array of numbers, defining the timing of each particle when using the "sequence" spawning strategy.| |strategy|"random"|Which spawning strategy to use. Possible values are: "random", "periodic" and "sequence".|

Particles options object

| Property | Default | Remarks | |---|---|---| |initialPosition|{x: { min: -10, max: 10 }, y: 0, z: 0 }|the initial position of spawned particles. Can be either a constant position, a vector where any of the values - x, y and z - are replaced with a range-object, or a function returning a vector. When using a range object a value between min and max (including), is picked at random for each particle.| |initialVelocity| { x: 0, y: {max: 100, min: 10} }|the initial velocity of spawned particles. Just like for the initial position, you can provide a constant, a vector containing ranges and/or constants, or a factory function. |lifetime|3|The lifetime of a single particle. Can be a constant, a range, or a factory function. When provding a range a new lifetime value is randomized for each new particle. If a function is provided, that function is evaluated for each new particle and the return value is used as the particles lifetime. |customDataFactory| optional|A function returning some unique custom data you want associated with each particle.|

MathUtils

A small collection of useful math functions | Name | Signature | Remark | | --- | --- | --- | | magnitude | magnitude(value: {x: number, y: number, z: number}):number | Returns the magnitude provided vector | | normalize | normalize(value: {x: number, y: number, z: number}):{x: number, y: number, z: number} | Returns a normalized vector pointing i the same direction as the provided vector. | | random | random(range:{min:number, max: number}):number | returns a random number in the specified range | | randomize | randomize<T>(values:T[]):T | returns a random object from the provided array |

MathUtils.Factories

Helper functions to create vector factories | Name | Signature | Remark | | --- | --- | --- | | circle | circle(r: number, inside?:boolean):() => {x: number, y: number, z: number} | Returns a factory that creates vectors either on the perimiter of a circle with radius r or inside that same circle, depending on the inside argument | | inDirectionOf | inDirectionOf(vec: {x: number, y: number, z?: number}, spread:number = 0, magFactorLimit = 1):() => {x: number, y: number, z: number} | Returns a function that creates vectors pointing in the general direction of vec. The created vectors falls withing the angle spread, centered around vec. The created vectors have a magnitude between the magnitude of vec and magFactorLimit times that original magnitude. |

utility functions

| Name | Signature | Remark | | --- | --- | --- | | SpawnOnDOMElement | SpawnOnDOMElement(options, particlesystemOptions, draw) | Registers event handlers on the provided element(s) that spawn a particle system on (or inside of) the target element. Note: The draw function takes an extra argument that gets passed the dynamically created canvas element.|

SpawnOnDOMElement options object

| Property | Default | Remarks | |---|---|---| |event| - |The DOM event that should trigger the start of a particle system.| |elements|-| a css query selector string, HTMLElement, NodeList or HTMLCollection that identify the relevant element(s).| |stopEvent|optional| Which event, if any, that should trigger a stop of the particle system.| |inside|false|A boolean indicating whether to position the particle system inside the element or outside / on top of it.| |inset|optional|Allows you to specify the number of pixels to inset the area of the emitter from the elements boundries.| |maxDistance| 200 |The number of pixels outside of the element that particles can travel before getting clipped. Only applicable when the inside property is false. | |canvasClassname|optional|A classname to assign to all canvases created as a result of a spawned particle system.|

Support or Contact

Having trouble? Create an issue in the github repository at Issues.