Introduction
On the Internet there are several articles about how to make a smoke effect, but the scripts are too "heavy", and for me personally not entirely clear. So I decided to simplify the task for those who are interested in implementing this effect in their projects.
I will not write a lot, mostly code with detailed comments.
So, the page layout:
<html> <head> <title>smoke</title> <meta charset="utf-8"/> </head> <body bgcolor = "#000"> <div id = "info" style = "color: #fff"></div> <canvas id = "canvas" width = "500px" height = "500px"></canvas> <script> ... </script> </body> </html>
This is all clear. There is a Canvas, a div for displaying the number of particles, and a body with a black background - on a white background the effect is not so beautiful.
Script in pieces:
First we need to find the necessary elements on the page, and write a convenient function for obtaining random values.
info = document.getElementById('info'); canvas = document.getElementById('canvas'); ctx = canvas.getContext('2d'); function random(min,max){ return Math.random() * (max - min) + min; }
After that you need to create an array of particles, the function of adding particles (in my code, my hand does not rise to write that this is a constructor =). Actually, the particles have the following properties:
- number
- transparency
- radius
- angle
- x-coordinate
- y-coordinate
- self draw function
Also to each of these properties there is a value that changes this property (sorry for the tautology).
Each particle is nothing but an object, and each object is an element of the particles array.
function addParticle(){ particles.push({ num: particles.length,
With the code above, I think everything is clear. Now the most interesting thing is drawing.
The technology is as follows: each particle independently calculates all its new parameters (coordinates, radius, etc.). After that, the canvas transparency changes (ctx.globalAlpha = ...). Further, for optimization, there is a check on how much time has passed from the previous rendering, and if this time is shorter than the frame time, then the drawing is performed, otherwise, only the parameters are calculated, so that with a large load the particles do not stand still. That is, it turns out such a samopisnaya frame skipping system. Well, the drawing itself, which includes changes in the initial coordinates for the correct rotation of the particle (ctx.translate), directly rotates the canvas around the X and Y coordinates (ctx.rotate (angle in radians)), and draws (ctx.drawImage). I draw attention to centering the image relative to the coordinates, drawing it needs to be “minus half the radius”, as well as remember to convert degrees to radians (angle * Math.PI / 180).
draw: function(timer){ this.op -= this.dop;
I hope that in this piece of code everything is explained very clearly.
The next piece is the run function for all particles. At the beginning, the call time is set (window.performance.now ()), then a new particle is added using the function described above, the canvas is cleaned, and the particle “runs” on each particle in the loop. At the same time, to automatically control the number of particles, we will delete the element of the array (particle) if the particle parameters are the same as when drawing it will not be visible (zero transparency, going beyond the limits of the canvas). The function is recursive and calls itself after all the renders. You ask why I do not use requestAnimationFrame? and I will answer that the smoke effect itself is used by me in an online game (almost added), and when using requestAnimationFrame, the function stops being called when switching to another browser tab, and accordingly all calculations cease to be performed, which is specifically for my game not allowed (all maps about the game until I will not open =). Actually code:
function draw(){ time = window.performance.now();
Here the writing of the code is over. It remains only to initialize the image and perform the first call of the “particle run” function. By the way, here's the picture:

And here is the code:
img = new Image(); img.src = 'smoke.png'; img.onload = draw();
Live link - by reference for clarity in various browsers changed window.performance.now () to Date.now (). It should now work on large devices.
PS Honestly I say - checked the work only in Chrome, if where it does not work - write.
Sources - F12.
I am waiting for adequate criticism of what is good and what is bad for you, but I hope each line is clear and correct. See you again!