---
description: "HTML5 Canvas particle fountain with gravity, color palettes, and fade trails. Beginner-friendly visual demo."
---

# Canvas Particle System

> **Difficulty:** Beginner | **Runtime:** HTML5 Canvas | **Output:** Visual animation

Teach an AI agent to generate a colorful particle fountain with gravity, fade trails, and configurable color palettes using the HTML5 Canvas API.

## What You'll Build

A self-contained HTML file that renders an animated particle system:
- Particles spray upward from the bottom center of the canvas
- Gravity pulls particles back down in natural arcs
- Each particle has a random color from a curated palette
- Particles fade out as they age, creating trailing effects
- The system runs at 60fps using `requestAnimationFrame`

## Core Concepts

### 1. Particle Data Structure

Each particle tracks position, velocity, age, and color:

```javascript
{
  x: number,       // horizontal position
  y: number,       // vertical position
  vx: number,      // horizontal velocity
  vy: number,      // vertical velocity
  life: number,    // remaining life (0-1)
  color: string,   // HSL color string
  size: number     // radius in pixels
}
```

### 2. Physics Update Loop

Every frame:
1. **Emit** new particles at the source point
2. **Update** each particle's position: `x += vx`, `y += vy`
3. **Apply gravity**: `vy += GRAVITY`
4. **Age** particles: `life -= decay`
5. **Remove** dead particles (`life <= 0`)
6. **Render** each particle as a filled circle with alpha = life

### 3. Color Palettes

Use HSL color space for vibrant, varied particles:
- **Fire:** hues 0-60 (red → yellow)
- **Ocean:** hues 180-240 (cyan → blue)
- **Forest:** hues 90-150 (green spectrum)
- **Rainbow:** random hue 0-360

## Instructions

1. Create an HTML file with a full-viewport `<canvas>`
2. Set up the animation loop with `requestAnimationFrame`
3. Implement the particle emitter at bottom-center
4. Add gravity and fade effects
5. Use semi-transparent background fills for trail effects

## Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `PARTICLE_COUNT` | 5 | Particles emitted per frame |
| `GRAVITY` | 0.15 | Downward acceleration |
| `SPEED` | 4 | Initial velocity magnitude |
| `DECAY` | 0.012 | Life reduction per frame |
| `TRAIL_ALPHA` | 0.08 | Background clear opacity (lower = longer trails) |

---

## Bundled Files

### `particles.html`

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas Particle System</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { background: #0a0a0a; overflow: hidden; }
  canvas { display: block; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const PARTICLE_COUNT = 5;
const GRAVITY = 0.15;
const SPEED = 4;
const DECAY = 0.012;
const TRAIL_ALPHA = 0.08;

const palettes = [
  () => `hsl(${Math.random() * 60}, 100%, ${50 + Math.random() * 30}%)`,
  () => `hsl(${180 + Math.random() * 60}, 80%, ${40 + Math.random() * 30}%)`,
  () => `hsl(${90 + Math.random() * 60}, 70%, ${35 + Math.random() * 30}%)`,
  () => `hsl(${Math.random() * 360}, 90%, ${50 + Math.random() * 20}%)`,
];
let currentPalette = palettes[3];
let particles = [];

function emit() {
  for (let i = 0; i < PARTICLE_COUNT; i++) {
    const angle = -Math.PI / 2 + (Math.random() - 0.5) * 1.2;
    const speed = SPEED * (0.5 + Math.random());
    particles.push({
      x: canvas.width / 2,
      y: canvas.height - 20,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 1,
      color: currentPalette(),
      size: 2 + Math.random() * 3,
    });
  }
}

function update() {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.x += p.vx;
    p.y += p.vy;
    p.vy += GRAVITY;
    p.life -= DECAY;
    if (p.life <= 0) particles.splice(i, 1);
  }
}

function draw() {
  ctx.fillStyle = `rgba(10, 10, 10, ${TRAIL_ALPHA})`;
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  for (const p of particles) {
    ctx.globalAlpha = p.life;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
}

function loop() {
  emit();
  update();
  draw();
  requestAnimationFrame(loop);
}

// Cycle palettes on click
canvas.addEventListener('click', () => {
  const idx = (palettes.indexOf(currentPalette) + 1) % palettes.length;
  currentPalette = palettes[idx];
});

window.addEventListener('resize', () => {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
});

loop();
</script>
</body>
</html>
```

### `README.md`

```md
# Canvas Particle System

A beginner-friendly HTML5 Canvas particle fountain demo.

## Running

Open `particles.html` in any modern browser. Click to cycle through color palettes.

## Customization

Edit the constants at the top of the script:
- `PARTICLE_COUNT` — particles per frame (higher = denser)
- `GRAVITY` — downward pull strength
- `SPEED` — initial burst velocity
- `DECAY` — how fast particles fade
- `TRAIL_ALPHA` — trail length (lower = longer)
```
