100% FREE TUTORIAL

3D PARTICLE
BLAST EFFECT

Build a click-to-fire explosion of glowing particles in the browser with Three.js — each one flies out with its own velocity, casts a flash of light, then fades away.

Three.js 3D particle blast explosion effect

By Shane Brumback · Beginner · JavaScript + Three.js

LIVE DEMO

Click anywhere on the background to fire a blast of glowing particles. Drag to orbit the camera around the grid. The whole effect is running live behind this page.

Introduction

A blast effect is the quickest way to make a 3D scene feel alive and game-like. The idea is simple: when the user clicks, spawn a burst of small glowing spheres at that point, give each a random outward velocity, and let them fly apart. Add a quick point-light flash for the "boom," then remove each particle after a short lifespan so the scene stays fast.

This free tutorial builds exactly that — an interactive, click-to-fire particle explosion with Three.js. It's the same technique you'd use for muzzle flashes, impacts, pickups, or spell effects in a browser game.

Frequently Asked Questions

How do you make an explosion effect in Three.js?

Spawn a burst of small meshes at the blast origin, each with a random outward velocity. Move them along their velocity every frame, add a point light for a glow flash, and remove each particle after a short lifespan. Firing the burst on a mouse click gives you an interactive explosion.

How do you fire particles from where the user clicks?

Convert the 2D mouse coordinates to a 3D point by building a normalized device vector and calling vector.unproject(camera). Cast from the camera through that point onto a plane to get the world-space origin, then spawn the particle burst there.

How do you clean up blast particles so they don't slow the scene?

Give each particle a lifespan. Use a timer to remove the mesh and its light from the scene after a couple of seconds, and splice it out of your tracking array. Also remove particles once they travel past a distance limit. This keeps the particle count bounded and the frame rate smooth.

Is this Three.js blast effect tutorial free?

Yes. This is a completely free tutorial. The full source code is shown on the page, you can copy it, and there is a live click-to-blast demo running in the background. No sign-in or membership is required.

How To Add The Code

Follow these steps to get the blast effect running. It uses OrbitControls from the Three.js addons, so it runs as an ES module with an import map.

  1. 1Create the HTML file with an importmap mapping three and three/addons/.
  2. 2Set up scene, camera & renderer and add a GridHelper so you can see depth.
  3. 3Add OrbitControls so you can drag to look around the blast.
  4. 4Convert clicks to 3D points — normalize the mouse position and unproject it through the camera to find the world-space blast origin.
  5. 5Spawn the burst — on mousedown, create ~50 small glowing spheres at the origin, each with a random normalized velocity.
  6. 6Add a flash — drop in a short-lived PointLight for the explosion glow.
  7. 7Animate & clean up — move each particle by its velocity every frame, and remove it (and its light) after ~2 seconds so the scene stays fast.
  8. 8Open it in your browser and click the background to fire.

The Full Code

Save as index.html next to your Three.js build/ and jsm/ folders, then serve the folder and click the scene.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Three.js Particle Blast Effect</title>
  <style> body { margin: 0; overflow: hidden; background: #000; } </style>
  <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
  <script type="importmap">
    { "imports": { "three": "./build/three.module.js", "three/addons/": "./jsm/" } }
  </script>
</head>
<body>
<script type="module">
  import * as THREE from 'three';
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
  camera.position.set(6, 4, 6);

  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(innerWidth, innerHeight);
  renderer.setClearColor(0x000000, 1);
  document.body.appendChild(renderer.domElement);

  const controls = new OrbitControls(camera, renderer.domElement);
  controls.enableZoom = false; // let the mouse wheel scroll the page instead of zooming
  controls.enablePan = false;
  scene.add(new THREE.GridHelper(100, 150));

  const particles = [];

  // Turn a mouse click into a 3D world point by raycasting onto the ground plane
  const raycaster = new THREE.Raycaster();
  const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
  const ndc = new THREE.Vector2();
  function getClickedPoint(clientX, clientY) {
    ndc.x = (clientX / window.innerWidth) * 2 - 1;
    ndc.y = -(clientY / window.innerHeight) * 2 + 1;
    raycaster.setFromCamera(ndc, camera);
    const hit = new THREE.Vector3();
    if (raycaster.ray.intersectPlane(groundPlane, hit)) return hit;
    return raycaster.ray.origin.clone().add(raycaster.ray.direction.clone().multiplyScalar(30));
  }

  // Fire a burst of glowing embers from the origin
  function fireBlast(origin) {
    // Bright flash at the blast center
    const light = new THREE.PointLight(0xffaa33, 40, 120);
    light.position.copy(origin);
    scene.add(light);
    setTimeout(() => scene.remove(light), 350);

    for (let i = 0; i < 120; i++) {
      const hue = THREE.MathUtils.randFloat(0.05, 0.14); // orange -> yellow
      const mesh = new THREE.Mesh(
        new THREE.SphereGeometry(THREE.MathUtils.randFloat(0.15, 0.4), 10, 10),
        new THREE.MeshBasicMaterial({ color: new THREE.Color().setHSL(hue, 1, 0.6) })
      );
      mesh.position.copy(origin);

      const dir = new THREE.Vector3(
        THREE.MathUtils.randFloat(-1, 1),
        THREE.MathUtils.randFloat(-1, 1),
        THREE.MathUtils.randFloat(-1, 1)
      ).normalize();
      const velocity = dir.multiplyScalar(THREE.MathUtils.randFloat(0.4, 1.1));

      scene.add(mesh);
      const p = { mesh, velocity };
      particles.push(p);

      setTimeout(() => {
        scene.remove(mesh);
        const idx = particles.indexOf(p);
        if (idx !== -1) particles.splice(idx, 1);
      }, 1400);
    }
  }

  // Use pointerdown in the CAPTURE phase so the blast fires even when OrbitControls
  // is also listening (it consumes pointerdown). Listen on window so a click anywhere works.
  window.addEventListener('pointerdown', e => {
    fireBlast(getClickedPoint(e.clientX, e.clientY));
  }, true);

  addEventListener('resize', () => {
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(innerWidth, innerHeight);
  });

  function animate() {
    requestAnimationFrame(animate);
    for (const p of particles) {
      p.mesh.position.add(p.velocity);
      p.velocity.y -= 0.012;          // gravity
      p.velocity.multiplyScalar(0.96); // drag
      p.mesh.scale.multiplyScalar(0.975); // shrink/fade
    }
    controls.update();
    renderer.render(scene, camera);
  }
  animate();
</script>
</body>
</html>

What To Try Next

  • Randomize each particle's color for a firework instead of a single hue.
  • Add gravity — subtract a little from velocity.y each frame so particles arc and fall.
  • Fade particles out by lowering material opacity over their lifespan.
  • Swap spheres for a sprite texture (a soft glow PNG) for a smoky look.
  • Trigger the blast from game events (a hit, a pickup) instead of only clicks.