Creating a Fire Particle Effect with JavaScript and Canvas


In this tutorial, we’re going to build a cool fire effect using plain JavaScript and the HTML5 <canvas> element. We will use only JavaScript ,and no external libraries or frameworks for the creative coding. Lets get started!

Prerequisites

Before we get started, make sure you have a basic understanding of HTML, CSS, and JavaScript.

  • Basic Understanding of HTML, CSS, and JavaScript: Familiarity with the fundamental concepts of HTML for structuring web pages, CSS for styling, and JavaScript for adding interactivity is essential. This tutorial assumes a basic knowledge of these languages.
  • Web Browser: Use a modern web browser like Google Chrome, Mozilla Firefox, or Microsoft Edge.

For the purpose of this tutorial, you can download the source files for the demo to help you follow along:

Note: As you test the source files, ensure that you load them within a suitable environment such that all the files are loaded. For offline use, we recommend that you use Live Server and vscode to load the files. Simply open the files in Vscode and run “index.html” using Live Server.

Demo

Here is what we are going to create.

Lets Get Started!

We start with a basic HTML page that includes a <canvas> element.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Fire Effect</title>
  </head>
  <body>
    <!-- Canvas for rendering the fire effect -->
    <canvas  id="canvas"  width="500px" height="500px" style="background: #222"></canvas>

    <script>
    
    </script>
  </body>
</html>

In the script tag, we can setup some initial variables:

      // Get canvas and context
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      const width = canvas.width;
      const height = canvas.height;

🧪 Step 1: Create the Fire Texture

To simulate glowing particles, we first create a soft texture using a temporary canvas. We will color the texture later.

// Generate a texture made of radial white blobs
      function create_texture(size) {
        const tempCanvas = document.createElement("canvas");
        tempCanvas.width = size;
        tempCanvas.height = size;
        const ctx = tempCanvas.getContext("2d");

        const centerX = size / 2;
        const centerY = size / 2;
        const blobCount = 300;

        ctx.fillStyle = "black";
        ctx.fillRect(0, 0, size, size);

        // Draw multiple radial white blobs
        for (let i = 0; i < blobCount; i++) {
          const angle = Math.random() * 2 * Math.PI;
          const distance = Math.random() * size * 0.4;

          const x = centerX + Math.cos(angle) * distance;
          const y = centerY + Math.sin(angle) * distance;
          const radius = 0.6 * size * 0.05;

          const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
          gradient.addColorStop(0, `rgba(255,255,255,${Math.random() * 0.5 + 0.3})`);
          gradient.addColorStop(1, `rgba(255,255,255,0)`);

          ctx.fillStyle = gradient;
          ctx.beginPath();
          ctx.arc(x, y, radius, 0, Math.PI * 2);
          ctx.fill();
        }

        // Central glow
        ctx.fillStyle = "#fff";
        ctx.arc(centerX, centerY + 2, size * 0.4, 0, Math.PI * 2);
        ctx.fill();

        const image = new Image();
        const data = ctx.getImageData(0, 0, size, size);
        image.src = tempCanvas.toDataURL();

        return { texture: image, data: data };
      }

🧭 Step 2: The Vector Class

The vector class is used to handle the position and movement of the particles. It allows us to add velocity (speed in a certain direction) and scale movement.

Here’s the vector class:

// Basic vector class for position and velocity
      class vector {
        constructor(x, y) {
          this.x = x;
          this.y = y;
        }
        add(vec) {
          this.x += vec.x;
          this.y += vec.y;
          return this;
        }
        copy() {
          return new vector(this.x, this.y);
        }
        scale(scale) {
          this.x *= scale;
          this.y *= scale;
        }
      }

How It Works:

  • add(vec): Adds the values of another vector to the current one (useful for moving the particle).
  • copy(): Returns a new vector with the same x and y values (useful for cloning the velocity).
  • scale(scale): Scales the vector by a certain factor, making it move faster or slower.

🎇 Step 3: Particle Class

Each texture drawn in the fire effect is a particle. It moves upward and fades out over time. Using the drawing function, we can color the particle texture by replacing the pixel colors. Particles fade from yellow to red to dark, giving the effect of fire.

// Represents a single particle in the fire
      class particle {
        constructor(system, speed, position, life, decay) {
          this.life = life;
          this.max_life = life;
          this.speed = speed.copy();
          this.decay = decay;
          this.parent = system;
          this.position = position.copy();
          system.particles.push(this);
        }

        draw() {
          const alpha = this.life / this.max_life;
          const pos = this.position;
          const size = this.parent.size;
          const textureData = this.parent.textureData;

          // Color based on life (red, orange, yellowish-white)
          let r = 255, g = 0, b = 0;
          if (alpha > 0.5) {
            const t = (alpha - 0.66) / (1 - 0.66);
            g = 165 + 90 * t;
          } else if (alpha > 0.33) {
            const t = (alpha - 0.33) / (0.66 - 0.33);
            g = 165 * t;
          } else {
            const t = alpha / 0.33;
            r = 50 + 205 * t;
            g = 50 + 205 * t * 0.5;
            b = 50 + 205 * t * 0.5;
          }

          const src = textureData.data.data;
          const recolored = ctx.getImageData(
            pos.x - size / 2,
            pos.y - size / 2,
            size,
            size
          );
          const dst = recolored.data;

          // Recolor bright parts of the texture
          for (let i = 0; i < src.length; i += 4) {
            const sr = src[i];
            const sg = src[i + 1];
            const sb = src[i + 2];

            const isWhite = (sr + sg + sb) / 3;
            if (isWhite >= 6) {
              dst[i] = r;
              dst[i + 1] = g;
              dst[i + 2] = b;
              dst[i + 3] = 250 * alpha;
            }
          }

          ctx.globalComposition = "destination-over";
          ctx.putImageData(recolored, pos.x - size / 2, pos.y - size / 2);
        }

        // Adds slight randomness to movement (flicker)
        add_ficker() {
          this.speed.x += (Math.random() - 0.5) * 0.001;
          this.speed.y += (Math.random() - 0.5) * 0.001;
        }

        // Update particle position and fade out
        update() {
          if (this.life > 0) {
            this.add_ficker();
            this.position.add(this.speed);
            this.draw();
            this.life -= this.decay;
          } else {
            const index = this.particles.indexOf(this);
            if (index !== -1) {
              this.particles.splice(index, 1);
            }
          }
        }
      }

🔥 Step 4: Particle System

The Particle System is the heart of our fire effect. It is responsible for managing, updating, and rendering all the individual particles (the glowing blobs that form the fire). The system controls how particles are emitted, how they move, how long they last, and how they interact with the environment.

The emit() function is responsible for creating new particles at regular intervals. Each time the emit() function is called (which happens every frame), the system adds emission new particles by calling the add() method.

Here’s the code for the particle system:

// Manages all particles and their behavior
      class particleSystem {
        constructor(position, emission, max_count, life = 1, size = 40, speed = 1) {
          this.max_count = max_count;
          this.emission = emission;
          this.particles = [];
          this.speed = speed;
          this.life = life + Math.random();
          this.decay = 0.05;
          this.position = position;
          this.size = size;
          this.color_buffer = document.createElement("canvas");
          this.color_buffer.width = size;
          this.color_buffer.height = size;
          this.textureData = create_texture(this.size);
          this.emit(); // Initial burst
        }

        // Emit particles each frame
        emit() {
          for (let i = 0; i < this.emission; i++) {
            this.add();
          }
        }

        // Create and add a new particle
        add() {
          if (this.particles.length >= this.max_count) return;

          const angle = Math.random() * Math.PI - Math.PI / 2;
          const speed = new vector(
            (Math.random() * 2 - 1) * 0.2,
            Math.random() * -1
          );
          speed.scale(this.speed);
          const position = this.position;
          const life = this.life;
          const decay = this.decay;

          new particle(this, speed, position, life, decay);
        }

        // Update all particles and remove dead ones
        update() {
          this.emit();
          this.particles = this.particles.filter(p => p.life > 0);
          for (let i = 0; i < this.particles.length; i++) {
            this.particles[i].update();
          }
        }
      }

🎞️ Step 5: Animate!

We use requestAnimationFrame to keep updating the scene.

// Create particle system positioned near bottom center
      const position = new vector(width * 0.5, height * 0.8);
      const particle_system = new particleSystem(position, 1, 1000, 10);

      // Clear the canvas each frame
      function clearScreen() {
        ctx.clearRect(0, 0, width, height);
      }

      // Animation loop
      function update() {
        clearScreen();
        ctx.globalAlpha = 1;
        particle_system.update();
        requestAnimationFrame(update); // Call next frame
      }

      update(); // Start animation

Conclusion

Hopefully, this breakdown helped you understand how to create dynamic effects with JavaScript! Try to make it your own and have fun experimenting! Don’t forget to share your version with us. Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *