Particle Background Effect with HTML Canvas

By Steve Jonk

2 min read

Create a dynamic animated particle background using the HTML5 Canvas API

Particle Background Effect with HTML Canvas
Authors

I always wanted to create a subtle, animated background effect, without relying on any external libraries. This React component uses only the HTML5 Canvas API to render a particle system with animated lines, making it lightweight, customizable, and easy to integrate into any modern UI.

What it looks like

Before I dive into the code, here’s a quick preview of what the effect looks like in action. Or you can view my personal site to see it in your browser: stevejonk.com.

Screen recording of the end result

The Component

The core of the effect is a React component (Background.tsx) that draws and animates particles on a <canvas>. Each particle moves independently, and lines are drawn between particles that are close to each other.

Key features:

  • Responsive: The canvas resizes with the window.
  • Customizable: Particle count, speed, color, and link radius are easy to tweak.
  • Efficient: Uses requestAnimationFrame for smooth animation.

How it Works

  • Particles: Each particle has a position, speed, direction, and color. They bounce off the canvas edges.
  • Lines: When two particles are within a certain distance, a line is drawn between them with opacity based on distance.
  • Animation Loop: The component uses requestAnimationFrame to update and redraw the scene.

Code Walkthrough

The main logic lives inside a useEffect hook, which sets up the canvas, particles, animation loop, and handles cleanup.

Canvas Setup

A ref is used to access the canvas, and its size is set to match the window. The canvas resizes responsively:

const canvasRef = (useRef < HTMLCanvasElement) | (null > null)

const resizeReset = () => {
  w = canvas.width = window.innerWidth
  h = canvas.height = window.innerHeight
}
window.addEventListener('resize', resizeReset)

Particle Class

Each particle is an instance of a class with position, speed, direction, and color. The update method moves the particle and bounces it off the edges:

class Particle {
  // ...
  update() {
    this.border()
    this.x += this.vector.x
    this.y += this.vector.y
  }
  border() {
    if (this.x >= w || this.x <= 0) this.vector.x *= -1
    if (this.y >= h || this.y <= 0) this.vector.y *= -1
    if (this.x >= w) this.x = w
    if (this.y >= h) this.y = h
    if (this.x < 0) this.x = 0
    if (this.y < 0) this.y = 0
  }
}

Initialization

Particles are initialized with random positions and speeds. The amount and link radius scale with the window size:

const initializeParticles = () => {
  options.particleAmount = (w + h) / 50
  options.linkRadius = w / 10 + h / 5
  particles = []
  for (let i = 0; i < options.particleAmount; i++) {
    particles.push(new Particle())
  }
}

Drawing and Animation

Each frame, the canvas is cleared, particles are updated and drawn, and lines are drawn between close particles with opacity based on distance:

const drawParticles = () => {
  particles.forEach((p) => {
    p.update()
    p.draw()
  })
}

const linkPoints = (point, hubs) => {
  hubs.forEach((hub) => {
    const distance = checkDistance(point.x, point.y, hub.x, hub.y)
    const opacity = 1 - distance / options.linkRadius
    if (opacity > 0) {
      ctx.strokeStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opacity})`
      ctx.beginPath()
      ctx.moveTo(point.x, point.y)
      ctx.lineTo(hub.x, hub.y)
      ctx.stroke()
    }
  })
}

const loop = () => {
  ctx.clearRect(0, 0, w, h)
  drawLines()
  drawParticles()
  loopId = requestAnimationFrame(loop)
}

Source Code

See the full code in components/Background.tsx in this repo.


This effect is a great way to add a modern, interactive touch to your site’s background without heavy dependencies.


Share