Canvas Particles JS

A smooth, highly efficient customizeable particle renderer

Installation

  • npm

    View the package on npm: canvasparticles-js.

    NPM downloads
    Console
    npm install canvasparticles-js

    ES Module import

    JavaScript (module)
    import CanvasParticles from 'canvasparticles-js'

    If you don't have a bundler:

    JavaScript (module)
    import CanvasParticles from './node_modules/canvasparticles-js/dist/index.mjs'

    Global import

    HTML
    <script src="./node_modules/canvasparticles-js/dist/index.umd.js" defer></script>

    No import required in each JavaScript file!

Start animating

Documentation: README.md

Options documentation: README.md#options

JavaScript
const selector = '#my-canvas'
const options = {}
new CanvasParticles(selector, options).start()

Showcase

Default

Instantiation of the CanvasParticles class with the default options.

JavaScript
const showcaseDefault = new CanvasParticles('#showcase-default').start()

Start and stop the animation.

Stop
showcaseDefault.stop()
Stop (no clear)
showcaseDefault.stop({clear: false})
Start
showcaseDefault.start()

Coloring

Setting the canvas and the particle colors.

JavaScript
new CanvasParticles('#showcase-coloring', {
  // Any CSS Supported value for the 'background' property
  background: 'rgb(21, 16, 25)',
  particles: {
    // Any CSS Supported color
    color: 'hsl(206, 100%, 77%)',
  },
}).start()

Interaction

Choose how the mouse can interact.

Info Setting mouse.distRatio below 1 allows particles to stick.

Important When using Stick, set particles.maxWork below 30 to prevent performance drops when collecting all particles in one place.

JavaScript
new CanvasParticles('#showcase-interact', {
  mouse: {
    interactionType: 2,
    connectDistMult: 0.8,
    distRatio: 0.7,
  },
  particles: {
      maxWork: 20,
  },
}).start()

Quantity

Define the amount of particles by setting particles.ppm.

Info ppm = Particles Per Million pixels the canvas covers.

JavaScript
new CanvasParticles('#showcase-quantity', {
  background: 'hsl(125, 42%, 35%)',
  particles: {
    ppm: 2000,
    max: 4000,
    connectDistance: 50,
  },
}).start()

Connect distance

Define the maximum length of a connection.

JavaScript
new CanvasParticles('#showcase-connect-distance', {
  background: 'linear-gradient(100deg, #f80, #0f8 150%)',
  mouse: {
    connectDistMult: 0.4,
  },
  particles: {
    color: '#0006',
    connectDistance: 400,
  },
}).start()

Movement

Customize the movement of the particles.

JavaScript
new CanvasParticles('#showcase-movement', {
  particles: {
    relSpeed: 3,
    relSize: 2,
    rotationSpeed: 40,
  },
}).start()

Pushing Gravity

Important Gravity requires heavy calculations.

JavaScript
new CanvasParticles('#showcase-pushing-gravity', {     
  particles: {
    ppm: 150,
    connectDistance: 175,
  },
  gravity: {
    repulsive: 3,
    fricion: 0.995,
  },
}).start()

Pulling Gravity

Also requires repulsive gravity to avoid forming a singularity.

JavaScript
const showcasePullingGravity =
new CanvasParticles('#showcase-pulling-gravity', {
  mouse: {
    distRatio: 1,
  },
  particles: {
    maxWork: Infinity
    connectDistance: 175,
    relSpeed: 0.2,
    rotationSpeed: 0.2,
  },
  gravity: {
    repulsive: 14,
    pulling: 4,
  },
})
Reset
showcasePullingGravity.newParticles()
Prevent heavy load
showcasePullingGravity.option.particles.maxWork = 12

Hue Rotation Effect

Use JavaScript to edit options on the fly.

JavaScript
const showcaseHueRotation =
new CanvasParticles('#showcase-hue-rotation', {
  background: 'var(--bg)',
  particles: {
    color: 'hsl(0, 100%, 50%)'
  },
}).start()
Rotate hue
setInterval(() => {
  const color = `hsl(${hue++}, 100%, 50%)`
  hue %= 360
  showcaseHueRotation.setParticleColor(color)
}, 20)

Multiple Colors

Overlap multiple <canvas> elements using CSS and initialize each one seperately.

JavaScript
new CanvasParticles('#showcase-multiple-colors-1', {
  background: 'black',
  particles: {
    color: 'yellow',
  },
}).start()

new CanvasParticles('#showcase-multiple-colors-2', {
  background: 'transparent',
  particles: {
    color: 'aqua',
  },
}).start()

Create custom particles

Use createParticle() to create particles at specific locations with custom directions, speeds and sizes.

Syntax: createParticle(posX, posY, dir, speed, size)

JavaScript
const scp = new CanvasParticles('#showcase-create-particles', {
  mouse: {
    interactionType: 1, // Don't actually move the particles
    connectDistMult: 150,
    distRatio: 1,
  },
  particles: {
    drawLines: false, // For performance in this case
    connectDistance: 1,
    rotationSpeed: 0,  // Very important!
  },
}).start()

const createParticleSineWaves = () => {
  scp.resizeCanvas()
  scp.newParticles({ keepAuto: true, keepManual: false })
  const w = scp.canvas.width - scp.offX * 2

  for (let x = 0; x < w; x += 0.5) {
    const y1 = Math.sin(x / 50) * 50
    const y2 = Math.cos(x / (w / 50)) * 50 + 250
    scp.createParticle(x + y1, y1 * 3, 0, 1, 2)
    scp.createParticle(x, y2, Math.PI / 2, 1, 3)
  }
}
createParticleSineWaves()
window.addEventListener('resize', createParticleSineWaves)

Sandbox

Presets

Persistent workspaces

JavaScript (editable)

        

Options documentation: README.md#options

Information

Handling particles beyond canvas bounds

Particles are only drawn if they are within the visible area of the canvas, accounting for their size. Particles are considered to still be visible if their edges are within the canvas bounds, preventing large particles from disappearing when their center crosses the edge.

When particles move beyond the floating area bounds, they will teleport to the opposite side.

Handling particle connections beyond canvas bounds

The floating area for the particles is larger than the canvas bounds to ensure no lines between particles will suddenly dissapear near the edges. The floating space added to all 4 sides is exactly equal to the maximum length of a line, defined with option.particles.connectDist.

By default, a line is not drawn between particles if both are outside the visible canvas area.

Each particle's grid position is calculated based on its location relative to the visible canvas area. This allows for determining whether two particles, even when outside the canvas, should have a connecting line drawn between them because part of it can still be seen.

Performance optimizations

Canvas Particles JS is a highly optimized particle renderer (ignoring the fact that it's coded in JavaScript 😭). A lot of time has gone into profiling the code and improving efficiency on expensive tasks.

  • The heaviest task of the animation loop is processing and assigning a new color to ctx.fillStyle for every single line being drawn, every frame, but the only difference between these colors is the opacity. Profiling shows that keeping ctx.fillStyle the same and only changing ctx.globalAlpha is about 25x as fast.

  • An IntersectionObserver is used to pause and resume the animation when the canvas enters or leaves the viewbox.

  • A ResizeObserver is used to handle the canvas width and height properties as it's more efficient than using events and it removes most forced reflow problems. This makes resizing roughly 18x faster based on benchmarks.

  • Particles smaller than one pixel are drawn as squares instead of circles which is ±183% faster.

Benchmark limits

After many improvements, the benchmark is able to handle ridiculously large numbers in seconds (device dependent):

Particles at once (depends on memory limit)
Total canvas area in pixels
Canvas elements
Over

Contact me

Do you think something is missing or did you spot a (small) mistake?
Don't hesitate to reach out and send me an email at:

kyle.hoeckman@gmail.com