1.1.2 ā€¢ Published 1 year ago

@lovetap/spray v1.1.2

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

@lovetap/spray

CI

Spray is a lightweight JavaScript library for creating particle animations that render to an HTML <canvas> element. Its purpose is to present a minimal and simple-to-extend API, for both ease of use and efficiency.

Installation

npm install @lovetap/spray

Or you can use a CDN:

<script src="https://unpkg.com/@lovetap/spray"></script>

Usage

To use Spray, create an instance of the Particles class and pass in a canvas element. Call the start method to begin the animation, and call addParticles any time to emit particles:

import Particles from "@lovetap/spray";

const canvas = document.getElementById("myCanvas");
const particles = new Particles({ canvas });
particles.start();
particles.addParticles(100); // this can come before start as well

The default particle behavior is to emit heart-emoji particles at random angles from the bottom center of the canvas, which will move up and fade out over time.

While this is a fine effect (the library is named after it), you may want particles that start from somewhere else, look and move differently, or are emitted at different rates (you may even want to render multiple particles for each one added). To do this, you can create (or customize) a "theme".

Theming

The behavior of Spray particles is defined a Theme object, which is set as the theme property on a Particles instance (you can also pass in a theme to the Particles constructor, but know that you can always update it).

A theme is comprised of two methods, generateParticles and updateParticles, and a delay which is used to space out calls to generateParticles (allowing for gradual particle emission).

Here is how we define the default theme:

import { Particles, Particle, Theme } from "@lovetap/spray";

type CustomParticle = Particle & { vx: number };

const sprayTheme: Theme = {
  /**
   * Emit at most one particle every 80ms.
   */
  delay: 80,

  /**
   * Render one particle for each particle added, and give each a random
   * horizontal velocity. All particles start out opaque, and begin their
   * journey from the bottom-center of the canvas.
   */
  generateParticles: (numParticles, canvasEl) => {
    /* Return a `particles` array and the number of particles that remain
     * to be emitted.
     */
    return {
      /**
       * If you wanted to emit all particles at once, you could return an
       * array with multiple particles in it.
       */
      particles: [
        {
          x: canvasEl.clientWidth / 2,
          y: canvasEl.clientHeight,
          opacity: 1,
          size: 24,
          particle: "ā™„ļø",
          vx: Math.random() * 3.5 * (Math.random() > 0.5 ? 1 : -1),
        },
      ],

      /**
       * You also can omit the `remaining` property and Spray will assume
       * that you are emitting one physical particle per logical particle.
       */
      remaining: numParticles - 1,
    };
  },

  /**
   * Move each particle up and to the left or right, and fade it out.
   *
   * Note that this function modifies the particles in-place (it does not
   * return a new array) before they are re-drawn each frame.
   */
  updateParticles: (particles) => {
    for (const particle of particles) {
      particle.x += (particle as CustomParticle).vx;
      particle.y -= 1;
      particle.opacity -= 0.01;
    }
  },
};

const canvas = document.getElementById("myCanvas");
const particles = new Particles({ canvas, theme: sprayTheme });
particles.start();

The generateParticles method takes a number of particles to generate and and a reference to the canvas element. You don't have to generate the exact number of particles requested, but you must return an object with a particles array and a remaining property that indicates how many particles are left to emit. The canvasEl argument is useful for calculating particle coordinates.

The updateParticles method takes an array of particles and a reference to the canvas element, and overwrites the particle properties with the ones that should be drawn next.

Customizing themes

Let's say you have a theme that you like, but you want to change something about the particles. Because particle behaviors are just functions, you can do this by creating a new theme object and overwriting the generateParticles method with a function that calls the original generateParticles and modifies the result:

import { Particles, Particle, Theme } from "@lovetap/spray";
import { sprayTheme } from "@lovetap/spray/themes";

const customSprayTheme: Theme = {
  ...sprayTheme,
  generateParticles: (numParticles, canvasEl) => {
    const { particles, remaining } = sprayTheme.generateParticles(
      numParticles,
      canvasEl
    );

    for (const particle of particles) {
      // The default theme uses hearts - let's try something else!
      particle.particle = "šŸ‘";
    }

    return { particles, remaining };
  },
};

Since it's just a function, you can also use a custom theme to add new particle behaviors. For example, let's say you want to emit particles that move in a circle. You can do this by creating a new theme that emits a single particle, and then updates it to move in a circle:

import { Particle, Theme } from "..";

type CustomParticle = Particle & { angle: number };

export const circleTheme: Theme = {
  delay: 80,
  generateParticles: (numParticles, canvasEl) => {
    return {
      particles: [
        {
          x: canvasEl.clientWidth / 2,
          y: canvasEl.clientHeight / 2,
          opacity: 1,
          size: 24,
          particle: "šŸ‘",
          angle: 0,
        },
      ],
      remaining: numParticles - 1,
    };
  },
  updateParticles: (particles, canvasEl) => {
    for (const particle of particles) {
      const { angle } = particle as CustomParticle;
      particle.x = Math.cos(angle) * 50 + canvasEl.clientWidth / 2;
      particle.y = Math.sin(angle) * 50 + canvasEl.clientHeight / 2;
      (particle as CustomParticle).angle += 0.1;
    }
  },
};

This example was generated by GitHub Copilot, and it worked first try (it's available in @lovetap/spray/themes/circle). In other words, it's pretty easy to create new particle behaviors!

Something to notice about this example is that it doesn't offer a way for particles to be destroyed. Spray will destroy particles once they become invisible: that is, when their opacity property is less than or equal to zero, or when their size property is less than or equal to zero, or when their x or y coordinates are outside the bounds of the canvas.

If we wanted to make sure that our particles were destroyed after they had completed their circle, we could add a check to the updateParticles method:

for (const particle of particles) {
  const { angle } = particle as CustomParticle;
  particle.x = Math.cos(angle) * 50 + canvasEl.clientWidth / 2;
  particle.y = Math.sin(angle) * 50 + canvasEl.clientHeight / 2;
  (particle as CustomParticle).angle += 0.1;

  if (angle > Math.PI * 2) {
    particle.opacity = 0;
  }
}

API

Particles

The Particles class is the main entry point for the library. It is responsible for rendering particles to a canvas element, and for updating the particles each frame.

constructor(options: ParticlesOptions);

The Particles constructor takes an options object with the following properties:

  • canvas: A reference to the canvas element that should be used to render particles.
  • theme: A theme object that defines how particles should be generated and updated.

start(): void;

Starts emitting particles.

addParticles(numParticles: number): void;

Emits additional particles.

Entry Points

There are two entry points for the library:

  • @lovetap/spray: The main entry point, which includes the Particles class and the Theme interface.

  • @lovetap/spray/themes: A collection of themes that can be used with the Particles class.

License

This library is licensed under the MIT license. See the LICENSE file for more details.

Ā© 2023 Lovetap LLC

1.1.2

1 year ago

1.1.1

1 year ago

1.1.0

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago