Beautiful HTML 5 Web page special effects learning notes _canvas to achieve flame following mouse

Keywords: Front-end Spark github Javascript Google

Effect:

  • Lifelike flames follow the mouse and sparks appear to illuminate the background text.
  • Drawing with canvas
  • javascript is used, but there is no complex logic. Beginning Degree: Simple
  • Welcome to my blog to read this article: https://clatterrr.com/archive...

Source code:

Learning Notes:

google font

As mentioned in the last article. Beautiful HTML 5 page special effects learning notes (3) guess what the next color is?
Step-by-step detailed explanation of javascript

Step 1:

Very simple initialization function.

var oCanvas;
init = function()
{
 
    oCanvas = new Fire();
    oCanvas.run();
}
window.onload = init;

Step 2:

Initialize canvas, define various basic things, and add event monitoring to the mouse. The third parameter of addEventListener means that if false, the order of event processing is first-in-advance, and true is second-in-advance. See for details https://www.runoob.com/jsref/...


    var Fire  = function(){
 
    this.canvas         = document.getElementById('fire');
    this.ctx             = this.canvas.getContext('2d');
    this.canvas.height     = window.innerHeight;
    this.canvas.width     = window.innerWidth;
 
    this.aFires         = [];
    this.aSpark         = [];
    this.aSpark2         = [];
 
    this.mouse = {
        x : this.canvas.width * .5,
        y : this.canvas.height * .75,
    }
    //Once the mouse moves, update this.mouse.x and this.mouse.y, because false is set, so the first-come events are handled first.
    this.canvas.addEventListener('mousemove', this.updateMouse.bind( this ), false);
}
Fire.prototype.updateMouse = function( e ){
    this.mouse.x = e.clientX;
    this.mouse.y = e.clientY;
}

Step 3:

Use the request Animation Frame to run the run function again for the next frame. Why not use setInterval? Because the intrinsic operating mechanism will make its running speed change with the performance of the randomizer, resulting in inaccurate time control. The request Animation Frame uses system time, guaranteeing 60 executions per second. Reference resources: https://www.cnblogs.com/xiaoh...

A new function is created by using the bind() method. When the bind() is called, this new function is specified by the first parameter of the bind, and the rest of the parameters will be used as parameters of the new function. Reference resources: https://developer.mozilla.org...

Fire.prototype.run = function(){
    //Renew Flames and Sparks
    this.update();
    this.draw();
    //Steady redrawing
    requestAnimationFrame( this.run.bind( this ) );
}

Step 4:

Renew a flame (actually a red circle) and two sparks (actually a small rectangle). If the life cycle of a flame or spark is over, delete it. Otherwise, keep updating it.
Note that flame sparks are stored in arrays, so delete slice.

Fire.prototype.update = function(){
 
    //Draw new flames (red circles) and sparks
    this.aFires.push( new Flame( this.mouse ) );
    this.aSpark.push( new Spark( this.mouse ) );
    this.aSpark2.push( new Spark( this.mouse ) );
   //If the previous element, the new flame (red circle) and the spark's life cycle are not complete, keep updating it or delete it.
    for (var i = this.aFires.length - 1; i >= 0; i--) {
        if( this.aFires[i].alive )
            this.aFires[i].update();
        else
            this.aFires.splice( i, 1 );
    }
    for (var i = this.aSpark.length - 1; i >= 0; i--) {
        if( this.aSpark[i].alive )
            this.aSpark[i].update();
        else
            this.aSpark.splice( i, 1 );
    }
    for (var i = this.aSpark2.length - 1; i >= 0; i--) {
        if( this.aSpark2[i].alive )
            this.aSpark2[i].update();
        else
            this.aSpark2.splice( i, 1 );
    }
}

Step 5:

Take the flame as the particle. Some comments are in the code. Firstly, the constructor is used to determine the parameters of the flame. If the flame is updated, its coordinates, life cycle, and color calculation using life cycle are updated. The color here is HSLA color. http://caibaojian.com/css3/va....
The same is true of spark structure parameters and update parameters.

var Flame = function( mouse ){
 
    //Mouse coordinates
    this.cx = mouse.x;
    this.cy = mouse.y;
    
    //Random generation around the mouse
    this.x = rand( this.cx - 25, this.cx + 25);
    this.y = rand( this.cy - 5, this.cy + 5);
    
    //Random Variables, Transverse and Longitudinal Axis and Radius Changes
    this.vy = rand( 1, 3 );
    this.vx = rand( -1, 1 );
    this.r = rand( 20, 30 );
    
    //life cycle
    this.life = rand( 3, 6 );
    this.alive = true;
    //Used to draw flame color
    this.c = {
        h : Math.floor( rand( 2, 40) ),
        s : 100,
        l : rand( 80, 100 ),
        a : 0,
        ta : rand( 0.8, 0.9 )
    }
}
 
Flame.prototype.update = function()
{
    //y coordinate change
    this.y -= this.vy;
    this.vy += 0.05;
   
    //x coordinate change
    this.x += this.vx;
    if( this.x < this.cx )
        this.vx += 0.1;
    else
        this.vx -= 0.1;
    
    //Radius change
    if(  this.r > 0 )
        this.r -= 0.1;
    if(  this.r <= 0 )
        this.r = 0;
 
 
    //Calculate the life cycle, calculate the flame color according to the life cycle
    this.life -= 0.15;
    if( this.life <= 0 ){
        this.c.a -= 0.05;
        if( this.c.a <= 0 )
            this.alive = false;
    }else if( this.life > 0 && this.c.a < this.c.ta ){
        this.c.a += .08;
    }
 
}

Step 6:

canvas's home, drawing background, including black background, text FIRE, and a dark red circle following the flame. This big circle, more like a projection on the wall, needs to be found carefully. canvas Reference https://www.w3school.com.cn/t... It's only by slowly searching for one function and one function that you can make a deep impression.
Note that the soft-light and color-dodge parameters of global Composite Operation seem to be unexplained on domestic websites, but only in English mdn. Reference resources:
https://developer.mozilla.org...

Fire.prototype.draw = function(){
 
    //Drawing Background
    this.ctx.globalCompositeOperation = "source-over";
    this.ctx.fillStyle = "rgba( 15, 5, 2, 1 )";
    this.ctx.fillRect( 0, 0, window.innerWidth, window.innerHeight );
    
    //Define Gradient Color Styles
    this.grd = this.ctx.createRadialGradient( this.mouse.x, this.mouse.y - 200,200,this.mouse.x, this.mouse.y - 100,0 );
    this.grd.addColorStop(0,"rgb( 15, 5, 2 )");
    this.grd.addColorStop(1,"rgb( 30, 10, 2 )");
    
    //Draw a circle and use the color gradient style. This circle is a big dark red circle.
    //The circle following the flame is more like a projection on the wall. It needs to be carefully observed.
    this.ctx.beginPath();
    this.ctx.arc( this.mouse.x, this.mouse.y - 100, 200, 0, 2*Math.PI );
    this.ctx.fillStyle= this.grd;
    this.ctx.fill();
    
    //Drawing text Fire
    this.ctx.font = "15em Amatic SC";
    this.ctx.textAlign = "center";
    this.ctx.strokeStyle = "rgb(50, 20, 0)";
    this.ctx.fillStyle = "rgb(120, 10, 0)";
    this.ctx.lineWidth = 2;
    this.ctx.strokeText("Fire",this.canvas.width/2, this.canvas.height * .72 );
    this.ctx.fillText("Fire",this.canvas.width/2, this.canvas.height * .72 );    
    
    //Drawing Flames
    this.ctx.globalCompositeOperation = "overlay";
    for (var i = this.aFires.length - 1; i >= 0; i--)this.aFires[i].draw( this.ctx );
    
    //Drawing Particles
    this.ctx.globalCompositeOperation = "soft-light";
    for (var i = this.aSpark.length - 1; i >= 0; i--)
    if( ( i % 2 ) === 0 )
            this.aSpark[i].draw( this.ctx );
 
    //Drawing Particle 2
    this.ctx.globalCompositeOperation = "color-dodge";
    for (var i = this.aSpark2.length - 1; i >= 0; i--) this.aSpark2[i].draw( this.ctx );
}

The last step is:

The most important but also very simple thing is to really draw flames and sparks. beginpath(),arc(),fillstyle(), and fill() are canvas methods. Look for information slowly. Just notice that the color here is hsla rather than rgba.

 
Flame.prototype.draw = function( ctx ){
 
    ctx.beginPath();
    ctx.arc( this.x, this.y, this.r * 3, 0, 2*Math.PI );
    ctx.fillStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + (this.c.a/20) + ")";
    ctx.fill();
 
    ctx.beginPath();
    ctx.arc( this.x, this.y, this.r, 0, 2*Math.PI );
    ctx.fillStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + this.c.a + ")";
    ctx.fill();
 
}

Full source code:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Canvas Realize Flame Following Mouse Animation</title>
 
<style>
@import url(https://fonts.googleapis.com/css?family=Amatic+SC);
html, body {
    margin:0;
    padding:0;
    height: 100%;
}
</style>
 
</head>
<body>
<div style="text-align:center;clear:both;">
</div>
<canvas id="fire"></canvas>
 
<script>
var Fire  = function(){
 
    this.canvas         = document.getElementById('fire');
    this.ctx             = this.canvas.getContext('2d');
    this.canvas.height     = window.innerHeight;
    this.canvas.width     = window.innerWidth;
 
    this.aFires         = [];
    this.aSpark         = [];
    this.aSpark2         = [];
 
 
 
    this.mouse = {
        x : this.canvas.width * .5,
        y : this.canvas.height * .75,
    }
    //Once the mouse moves, update this.mouse.x and this.mouse.y, because false is set, so the first-come events are handled first.
    this.canvas.addEventListener('mousemove', this.updateMouse.bind( this ), false);
 
}
Fire.prototype.run = function(){
    //Renew Flames and
    this.update();
    this.draw();
 
    //Steady redrawing
    requestAnimationFrame( this.run.bind(this));
 
}
Fire.prototype.update = function(){
 
    //Draw new flames (red circles) and sparks
    this.aFires.push( new Flame( this.mouse ) );
    this.aSpark.push( new Spark( this.mouse ) );
    this.aSpark2.push( new Spark( this.mouse ) );
 
 
 
    //If the previous element, the new flame (red circle) and the spark's life cycle are not complete, keep updating it or delete it.
    for (var i = this.aFires.length - 1; i >= 0; i--) {
        if( this.aFires[i].alive )
            this.aFires[i].update();
        else
            this.aFires.splice( i, 1 );
 
    }
 
    for (var i = this.aSpark.length - 1; i >= 0; i--) {
        if( this.aSpark[i].alive )
            this.aSpark[i].update();
        else
            this.aSpark.splice( i, 1 );
    }
 
 
    for (var i = this.aSpark2.length - 1; i >= 0; i--) {
        if( this.aSpark2[i].alive )
            this.aSpark2[i].update();
        else
            this.aSpark2.splice( i, 1 );
    }
 
}
 
Fire.prototype.draw = function(){
 
    //Drawing Background
    this.ctx.globalCompositeOperation = "source-over";
    this.ctx.fillStyle = "rgba( 15, 5, 2, 1 )";
    this.ctx.fillRect( 0, 0, window.innerWidth, window.innerHeight );
    
    //Define Gradient Color Styles
    this.grd = this.ctx.createRadialGradient( this.mouse.x, this.mouse.y - 200,200,this.mouse.x, this.mouse.y - 100,0 );
    this.grd.addColorStop(0,"rgb( 15, 5, 2 )");
    this.grd.addColorStop(1,"rgb( 30, 10, 2 )");
    
    //Draw a circle and use the color gradient style. This circle is a big dark red circle.
    //The circle following the flame is more like a projection on the wall. It needs to be carefully observed.
    this.ctx.beginPath();
    this.ctx.arc( this.mouse.x, this.mouse.y - 100, 200, 0, 2*Math.PI );
    this.ctx.fillStyle= this.grd;
    this.ctx.fill();
    
    //Drawing text Fire
    this.ctx.font = "15em Amatic SC";
    this.ctx.textAlign = "center";
    this.ctx.strokeStyle = "rgb(50, 20, 0)";
    this.ctx.fillStyle = "rgb(120, 10, 0)";
    this.ctx.lineWidth = 2;
    this.ctx.strokeText("Fire",this.canvas.width/2, this.canvas.height * .72 );
    this.ctx.fillText("Fire",this.canvas.width/2, this.canvas.height * .72 );    
    
    //Drawing Flames
    this.ctx.globalCompositeOperation = "overlay";
    for (var i = this.aFires.length - 1; i >= 0; i--)this.aFires[i].draw( this.ctx );
    
    //Drawing Particles
    this.ctx.globalCompositeOperation = "soft-light";
    for (var i = this.aSpark.length - 1; i >= 0; i--)
    if( ( i % 2 ) === 0 )
            this.aSpark[i].draw( this.ctx );
 
    //Drawing Particle 2
    this.ctx.globalCompositeOperation = "color-dodge";
    for (var i = this.aSpark2.length - 1; i >= 0; i--) this.aSpark2[i].draw( this.ctx );
}
 
Fire.prototype.updateMouse = function( e ){
    this.mouse.x = e.clientX;
    this.mouse.y = e.clientY;
}
 
 
 
 
 
 
 
 
var Flame = function( mouse ){
 
    //Mouse coordinates
    this.cx = mouse.x;
    this.cy = mouse.y;
    
    //Random generation around the mouse
    this.x = rand( this.cx - 25, this.cx + 25);
    this.y = rand( this.cy - 5, this.cy + 5);
    
    //Random Variables, Transverse and Longitudinal Axis and Radius Changes
    this.vy = rand( 1, 3 );
    this.vx = rand( -1, 1 );
    this.r = rand( 20, 30 );
    
    //life cycle
    this.life = rand( 3, 6 );
    this.alive = true;
    //Used to draw flame color
    this.c = {
        h : Math.floor( rand( 2, 40) ),
        s : 100,
        l : rand( 80, 100 ),
        a : 0,
        ta : rand( 0.8, 0.9 )
    }
}
 
Flame.prototype.update = function()
{
    //y coordinate change
    this.y -= this.vy;
    this.vy += 0.05;
   
    //x coordinate change
    this.x += this.vx;
    if( this.x < this.cx )
        this.vx += 0.1;
    else
        this.vx -= 0.1;
    
    //Radius change
    if(  this.r > 0 )
        this.r -= 0.1;
    if(  this.r <= 0 )
        this.r = 0;
 
 
    //Calculate the life cycle, calculate the flame color according to the life cycle
    this.life -= 0.15;
    if( this.life <= 0 ){
        this.c.a -= 0.05;
        if( this.c.a <= 0 )
            this.alive = false;
    }else if( this.life > 0 && this.c.a < this.c.ta ){
        this.c.a += .08;
    }
 
}
Flame.prototype.draw = function( ctx ){
 
    ctx.beginPath();
    ctx.arc( this.x, this.y, this.r * 3, 0, 2*Math.PI );
    ctx.fillStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + (this.c.a/20) + ")";
    ctx.fill();
 
    ctx.beginPath();
    ctx.arc( this.x, this.y, this.r, 0, 2*Math.PI );
    ctx.fillStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + this.c.a + ")";
    ctx.fill();
 
}
 
 
 
 
 
 
 
 
var Spark = function( mouse ){
 
    this.cx = mouse.x;
    this.cy = mouse.y;
    this.x = rand( this.cx -40, this.cx + 40);
    this.y = rand( this.cy, this.cy + 5);
    this.lx = this.x;
    this.ly = this.y;
    this.vy = rand( 1, 3 );
    this.vx = rand( -4, 4 );
    this.r = rand( 0, 1 );
    this.life = rand( 4, 5 );
    this.alive = true;
    this.c = {
 
        h : Math.floor( rand( 2, 40) ),
        s : 100,
        l : rand( 40, 100 ),
        a : rand( 0.8, 0.9 )
 
    }
 
}
Spark.prototype.update = function()
{
 
    this.lx = this.x;
    this.ly = this.y;
 
    this.y -= this.vy;
    this.x += this.vx;
 
    if( this.x < this.cx )
        this.vx += 0.2;
    else
        this.vx -= 0.2;
 
    this.vy += 0.08;
    this.life -= 0.1;
    if( this.life <= 0 ){
        this.c.a -= 0.05;
        if( this.c.a <= 0 )
            this.alive = false;
    }
 
}
Spark.prototype.draw = function( ctx ){
 
    ctx.beginPath();
    ctx.moveTo( this.lx , this.ly);
    ctx.lineTo( this.x, this.y);
    ctx.strokeStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + (this.c.a / 2) + ")";
    ctx.lineWidth = this.r * 2;
    ctx.lineCap = 'round';
    ctx.stroke();
    ctx.closePath();
 
    ctx.beginPath();
    ctx.moveTo( this.lx , this.ly);
    ctx.lineTo( this.x, this.y);
    ctx.strokeStyle = "hsla( " + this.c.h + ", " + this.c.s + "%, " + this.c.l + "%, " + this.c.a + ")";
    ctx.lineWidth = this.r;
    ctx.stroke();
    ctx.closePath();
 
}
 
rand = function( min, max ){ return Math.random() * ( max - min) + min; };
onresize = function () { oCanvas.canvas.width = window.innerWidth; oCanvas.canvas.height = window.innerHeight; };
 
 
 
var oCanvas;
init = function()
{
 
    oCanvas = new Fire();
    oCanvas.run();
}
window.onload = init;
</script>
 
</body>
</html>

Posted by rich___ on Tue, 27 Aug 2019 03:44:52 -0700