Creating a particle effect using PIXI.js
PixiJS is a famous WebGL-wrapper, or in other words, a thing that gives you easy access to high graphics performance on a HTML canvas. I have seen a lot of beautiful hero canvases and thought something like that could work for my personal website too. I modified a pretty well-written codepen to produce this effect I'm quite happy with. I'll just go ahead and briefly describe how it works.
Initialization
I wanted to use an existing canvas for my PIXI app, which can be achieved as follows.
const canvas = document.getElementById("banner_canvas");
var app = new PIXI.Application(window.innerWidth,
window.innerHeight,
{view: canvas,
antialias: true,
transparent: true});
For performance and improved control over the particles, they are put into a container.
const drops = new PIXI.Container();
app.stage.addChild(drops)
We want to draw circles. It's possible to use PIXI.Graphics
for drawing shapes
such as circles. However, it's very inefficient to execute a new graphics fill
on each draw call. A much better approach is to proceed as follows:
- Draw a circle using
PIXI.Graphics
. - Save the circle in a texture.
- Use the texture to create a sprite for each particle.
// Create a base graphic for our sprites
const p = new PIXI.Graphics()
p.beginFill(COLOR)
p.drawCircle(0, 0, 100)
p.endFill()
// Generate a base texture from the base graphic
const baseTexture = app.renderer.generateTexture(p)
let particles = genParticles(baseTexture)
The particle generator and the updater
The particle generator works like this (paraphrased), nothing that interesting going on there.
const genParticles = (texture) =>
new Array(AMOUNT).fill().map(p => {
var p = new PIXI.Sprite(texture);
// Spawn a particle at a random position
p.x = Math.random()*window.innerWidth;
p.y = Math.random()*window.innerHeight;
// Set an initial velocity.
p.vx = 1;
drops.addChild(p);
return p;
});
The updater has some nice tricks.
app.ticker.add(function(delta) {
if (
app.renderer.height !== innerHeight ||
app.renderer.width !== innerWidth
) {
app.renderer.resize(innerWidth, innerHeight)
drops.removeChildren()
particles = genParticles(baseTexture)
}
for(let p of particles){
p.x += delta*p.vx;
p.y += delta*p.vy;
if(p.x >= window.innerWidth + 20){
p.x = -20;
}
}
app.renderer.render(drops);
});
This will reset the animation and regenerate everything in case the window is resized so that the animation will not appear distorted.
if (
app.renderer.height !== innerHeight ||
app.renderer.width !== innerWidth
) {
app.renderer.resize(innerWidth, innerHeight)
drops.removeChildren()
particles = genParticles(baseTexture)
}
This will put a particle that leaves the screen from the right slightly outside the left side of the screen. This will give an impression of an infinite amount of particles.
if(p.x >= window.innerWidth + 20){
p.x = -20;
}
Conclusion
Since the resulting animation is pretty intense for a hero element,
I combined it with some CSS masking to keep things under control. WebGL is
really powerful since I didn't observe any lag with AMOUNT=2000
. All in all,
I had a lot of fun making this, and I strongly recommend trying PIXI.js if you
haven't already.