@lovetap/spray
v1.1.2
Published
A canvas particle emitter
Downloads
10
Readme
@lovetap/spray
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 theParticles
class and theTheme
interface.@lovetap/spray/themes
: A collection of themes that can be used with theParticles
class.
License
This library is licensed under the MIT license. See the LICENSE
file for more details.
© 2023 Lovetap LLC