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!