Build a Simple Canvas Particle Engine from Scratch
This tutorial walks you through creating a lightweight Canvas particle engine—including world, launcher, and grain components, physics forces, rendering loop, and core JavaScript modules—so you can experiment with interactive particle effects in web projects.
Introduction
We’ll build a simple Canvas particle engine (called “engine”) and provide a demo QR code for testing.
Conceptual Overview
The engine consists of three elements: World, Launcher, and Grain (particle). The Launcher lives inside the World, creates particles, and both World and Launcher affect particle state; each particle updates its position each tick and draws itself.
World
The “World” represents the global environment that influences all particles residing in it.
Launcher
A Launcher emits particles and controls their initial properties such as position, size, lifespan, and whether they are affected by World or Launcher forces. It also removes dead particles.
Grain (Particle)
A Grain is the smallest unit with its own position, size, lifespan, and other attributes needed for rendering on Canvas.
Main Particle Rendering Logic
The main loop updates the world, clears the canvas, draws the background, and iterates over all launchers to update their status, create new grains, and paint them.
Update world state.
Clear canvas and redraw background.
Update each launcher, create grains, and paint them.
Creating a World
The World includes gravity, heat, and wind forces (gravity vertical, heat vertical, wind horizontal) and a time counter for pause/reverse effects.
define(function(require, exports, module) {
var Util = require('./Util');
var Launcher = require('./Launcher');
// World constructor and methods (omitted for brevity)
});The timeTick method is called each animation frame to update status, clear canvas, draw background, and invoke each launcher's update, grain creation, and painting.
World.prototype.timeTick = function(){
this.updateStatus();
this.context.clearRect(0,0,this.canvas.width,this.canvas.height);
this.drawBackground();
for(var i = 0;i<this.launchers.length;i++){
this.launchers[i].updateLauncherStatus();
this.launchers[i].createGrain(1);
this.launchers[i].paintGrain();
}
};The world’s updateStatus increments time and randomizes wind and heat.
World.prototype.updateStatus = function(){
this.time+=this.timeProgress;
this.wind = Util.randomFloat(this.minWind,this.maxWind);
this.heat = Util.randomFloat(this.minHeat,this.maxHeat);
};Creating a Launcher
The Launcher constructor receives configuration such as id, host world, grain image, lifespans, position ranges, size ranges, mass, heat, wind, and flags indicating whether particles are influenced by world or launcher forces.
define(function (require, exports, module) {
var Util = require('./Util');
var Grain = require('./Grain');
// Launcher constructor and methods (omitted for brevity)
});Key methods:
Launcher.prototype.createGrain = function (count) {
// create grains respecting maxAliveCount
};
Launcher.prototype.swipeDeadGrain = function (grain_id) {
// remove dead grain and create a new one
};
Launcher.prototype.paintGrain = function () {
// draw each grain
};
Launcher.prototype.updateLauncherStatus = function () {
// randomize wind and heat if enabled
};Particle (Grain) Implementation
The Grain constructor defines properties like position, velocity, size, mass, life, birthTime, color, alpha, and influence flags.
define(function (require, exports, module) {
var Util = require('./Util');
// Grain constructor and methods (omitted for brevity)
});Core methods calculate motion based on gravity, heat, and wind, update position and alpha, determine death, and render the particle.
Grain.prototype.calculate = function () {
if (this.influencedByWorldGravity) {
this.vy += this.world.gravity+Util.randomFloat(0,0.3*this.world.gravity);
}
if (this.influencedByWorldHeat && this.world.heatEnable) {
this.vy -= this.world.heat+Util.randomFloat(0,0.3*this.world.heat);
}
if (this.influencedByLauncherHeat && this.launcher.heatEnable) {
this.vy -= this.launcher.heat+Util.randomFloat(0,0.3*this.launcher.heat);
}
if (this.influencedByWorldWind && this.world.windEnable) {
this.vx += this.world.wind+Util.randomFloat(0,0.3*this.world.wind);
}
if (this.influencedByLauncherWind && this.launcher.windEnable) {
this.vx += this.launcher.wind+Util.randomFloat(0,0.3*this.launcher.wind);
}
this.y += this.vy;
this.x += this.vx;
this.alpha = this.initAlpha * (1 - (this.world.time - this.birthTime) / this.life);
};
Grain.prototype.isDead = function () {
return Math.abs(this.world.time - this.birthTime) > this.life;
};
Grain.prototype.paint = function () {
if (this.isDead()) {
this.launcher.swipeDeadGrain(this.id);
} else {
this.calculate();
this.world.context.save();
this.world.context.globalCompositeOperation = 'lighter';
this.world.context.globalAlpha = this.alpha;
this.world.context.drawImage(this.launcher.grainImage, this.x-(this.sizeX)/2, this.y-(this.sizeY)/2, this.sizeX, this.sizeY);
this.world.context.restore();
}
};Future Work
The prototype can be extended into a visual editor for broader use.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent TDS Service
TDS Service offers client and web front‑end developers and operators an intelligent low‑code platform, cross‑platform development framework, universal release platform, runtime container engine, monitoring and analysis platform, and a security‑privacy compliance suite.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
