start

End result: Codepen

It starts as a single use case, positioned in the center of the canvas, and then expands

Get the Canvas element and visual width first

    let canvas = document.querySelector('#canvas')
    let context = canvas.getContext('2d')
    let cw = canvas.width = window.innerWidth
    let ch = canvas.height = window.innerHeight
Copy the code

Began to draw

Part I – Scintillating circles for positioning

// Create a flicker circle class
class  Kirakira {
    constructor() {// The target point is the center of the screen
        this.targetLocation = {x: cw/2.y: ch/2}
        this.radius = 1
    }
    draw() {
        // Draw a circle
        context.beginPath()
        context.arc(this.targetLocation.x, this.targetLocation.y, 5.0.Math.PI * 2)
        context.lineWidth = 2
        context.strokeStyle = '#FFFFFF';
        context.stroke()
    }

    update(){
        if(this.radius < 5) {this.radius += 0.3
        }else{
            this.radius = 1
        }
    }

    init() {
        this.draw()
    }
}

class Animate {
    run() {
        window.requestAnimationFrame(this.run.bind(this))
        if(o){
            o.init()
        }
    }
}

let o = new Kirakira()
let a = new Animate()
a.run()
Copy the code

Thus, you can see a circle expanding from small to large. Because the previous frame is not erased, the result of each frame is displayed, so it is a solid circle. I want to draw a flickering circle, so I can erase the previous frame.

context.clearRect(0.0, cw, ch)
Copy the code

Part two – Drawing rays

First, draw an extension line from the bottom to the center of the canvas. Since it’s an extended line of motion, it has at least one starting coordinate and one ending coordinate

class Biubiubiu {
    constructor(startX, startY, targetX, targetY){
        this.startLocation = {x: startX, y: startY}
        // Move the current coordinate. The initial default is the starting coordinate
        this.nowLoaction = {x: startX, y: startY}
        this.targetLocation = {x: targetX, y: targetY}
    }
    draw(){
        context.beginPath()
        context.moveTo(this.startLocation.x, this.startLocation.y)
        context.lineWidth = 3
        context.lineCap = 'round'
        // The line needs to be positioned to the current motion coordinates to make the line move
        context.lineTo(this.nowLoaction.x, this.nowLoaction.y)
        context.strokeStyle = '#FFFFFF'
        context.stroke()   
    }
    update(){}
    init(){
        this.draw()
        this.update()
    }
}
class Animate {
    run() {
        window.requestAnimationFrame(this.run.bind(this))
        context.clearRect(0.0, cw, ch)
        if(b){
            b.init()
        }
    }
}
// The intention here is to position the starting point randomly at the bottom of the canvas and the ending point in the center of the canvas.
let b = new Biubiubiu(Math.random()*(cw/2), ch, cw/2, ch/2)
let a = new Animate()
a.run()
Copy the code

Let’s talk about trigonometry

So given the coordinates of the beginning and the end, the question is, how do you know the coordinates of each frame from the beginning to the end

  1. Whether the distance of the line movement exceeds the distance from the starting point to the end point, if so, it needs to stop the movement
  2. Coordinates that each frame of motion arrives at

Calculation of distance

For the calculation of the distance between coordinates, it is obvious that the Pythagorean theorem can be used. Distance = √(x1-x0)² + (y1-y0)² SQRT (math.pow ((x1-x0), 2) + math.pow ((y1-y0), 2))

Calculate the coordinate

The total distance from the previous frame (d) + the distance traveled in the current frame (v) = the distance from the current frame (d). Suppose a speed = 2, the Angle formed by the starting point and the ending point is (θ), and the coordinates of the distance (v) are vx, vy, then vx = cos(θ) * speed, Vy = sin(θ) * speed Since the starting point (x0, y0) and the ending point (x1, y1) are known, it can be seen from the figure that the Angle between the two points and the horizontal line can be obtained by using the tangent of the trigonometric function. The code is represented as math.atan2 (y1-y0, x1-x0).

Back to the code to draw the extension line. Add Angle and distance calculations to the Biubiubiu class,

class Biubiubiu {
    constructor(startX, startY, targetX, targetY){
        ...
        // The distance to the target
        this.targetDistance = this.getDistance(this.startLocation.x, this.startLocation.y, this.targetLocation.x, this.targetLocation.y);
        / / speed
        this.speed = 2
        / / Angle
        this.angle = Math.atan2(this.targetLocation.y - this.startLocation.y, this.targetLocation.x - this.startLocation.x)
        // Whether the target point is reached
        this.arrived = false
    }
    
    draw(){ ... }
    
    update(){
        // Calculate the current frame distance v
        let vx = Math.cos(this.angle) * this.speed
        let vy = Math.sin(this.angle) * this.speed
        // Calculate the current movement distance
        let nowDistance = this.getDistance(this.startLocation.x, this.startLocation.y, this.nowLoaction.x+vx, this.nowLoaction.y+vy)
        // If the current movement is beyond the target distance, no further movement is required
        if(nowDistance >= this.targetDistance){
            this.arrived = true
        }else{
            this.nowLoaction.x += vx
            this.nowLoaction.y += vy
            this.arrived = false
        }
    }
    
    getDistance(x0, y0, x1, y1) {
        // Calculate the distance between two coordinate points
        let locX = x1 - x0
        let locY = y1 - y0
        // Pythagorean theorem
        return Math.sqrt(Math.pow(locX, 2) + Math.pow(locY, 2))
    }
    
    init(){
        this.draw()
        this.update()
    }
}
class Animate {... }// The intention here is to position the starting point randomly at the bottom of the canvas and the ending point in the center of the canvas.
let b = new Biubiubiu(Math.random()*(cw/2), ch, cw/2, ch/2)
let a = new Animate()
a.run()
Copy the code

Since speed is fixed, this is a constant motion. An acceleration can be added to change it to variable motion. My target effect is not an entire line, but a line track that is currently running. Here is an idea, a certain amount of coordinate points can be stored as an array, in the drawing process, the coordinate in the array can point to the coordinate of the current movement, and in the change of the number of frames to keep the array data replacement, so that a small segment of the movement of the line can be drawn

Implementation code:

class Biubiubiu {
    constructor(startX, startY, targetX, targetY) {
        ...
        // Set the line segment to 10 frames
        this.collection = new Array(10)
    }
    draw() {
        context.beginPath()
        // start with the first digit of the set
        try{
            context.moveTo(this.collection[0] [0].this.collection[0] [1])}catch(e){
            context.moveTo(this.nowLoaction.x, this.nowLoaction.y)
        }
        ...
    }
    
    update(){
        // Perform data substitution on the set, pop up the first data in the array, and push the coordinate of the current movement to the set. If you take the first and last coordinates of the array, that's 10 frames
        this.collection.shift()
        this.collection.push([this.nowLoaction.x, this.nowLoaction.y])
        // Add acceleration to speed
        this.speed *= this.acceleration ... }}Copy the code

Part three – Draw an explosion effect

According to the code of the extension line above, if you do not take 10 frames, take a small line segment of two or three frames, and then change the direction of extension, a combination of multiple rays can form an explosion effect. Spark will be affected by gravity, friction and so on. The diffusion trend is biased downward, so some gravity and friction coefficient need to be added

class Boom {
    // Explosives have no definite end point coordinates, which can be defined by setting a certain threshold
    constructor(startX, startY){
        this.startLocation = {x: startX, y: startY}
        this.nowLocation = {x: startX, y: startY}
        / / speed
        this.speed = Math.random()*10+2
        / / acceleration
        this.acceleration = 0.95
        // There is no definite end point, so there is no fixed Angle, can be random Angle diffusion
        this.angle = Math.random()*Math.PI*2
        // Set the threshold to 100
        this.targetCount = 100
        // Currently calculated as 1 to determine whether the threshold will be exceeded
        this.nowNum = 1
        / / transparency
        this.alpha = 1
        // Gravity coefficient
        this.gravity = 0.98
        this.decay = 0.015
        
        // Set the line segment to 10 frames
        this.collection = new Array(CONFIG.boomCollectionCont)
        
        // Whether the target point is reached
        this.arrived = false
    }

    draw(){
        context.beginPath()
        try{
            context.moveTo(this.collection[0] [0].this.collection[0] [1])}catch(e){
            context.moveTo(this.nowLocation.x, this.nowLocation.y)
        }
        context.lineWidth = 3
        context.lineCap = 'round'
        context.lineTo(this.nowLocation.x, this.nowLocation.y)
        // Set the fade effect caused by transparency reduction to look less abrupt
        context.strokeStyle = `rgba(255, 255, 255, The ${this.alpha}) `
        context.stroke()
    }

    update(){
        this.collection.shift()
        this.collection.push([this.nowLocation.x, this.nowLocation.y])
        this.speed *= this.acceleration
        
        let vx = Math.cos(this.angle) * this.speed
        // Add the coefficient of gravity, and the trajectory tends downward
        let vy = Math.sin(this.angle) * this.speed + this.gravity

        // When the current calculation is greater than the threshold, the fading process begins
        if(this.nowNum >= this.targetCount){
            this.alpha -= this.decay
        }else{
            this.nowLocation.x += vx
            this.nowLocation.y += vy
            this.nowNum++
        }

        // If the transparency is 0, it can be removed to free up space
        if(this.alpha <= 0) {this.arrived = true
        }
    }

    init(){
        this.draw()
        this.update()
    }
}

class Animate {
    constructor() {// Define an array as a collection of explosive points
        this.booms = []
        // To avoid excessive drawing on every frame, set the threshold and draw when the threshold is reached
        this.timerTarget = 80
        this.timerNum = 0
    }
    
    pushBoom(){
        // Instantiate explosion effect, random number of ray diffusion
        for(let bi = Math.random()*10+20; bi>0; bi--){
            this.booms.push(new Boom(cw/2, ch/2))
        }
    }

    run() {
        window.requestAnimationFrame(this.run.bind(this))
        context.clearRect(0.0, cw, ch)
        
        let bnum = this.booms.length
        while(bnum--){
            // Trigger animation
            this.booms[bnum].init()
            if(this.booms[bnum].arrived){
                // After reaching the target transparency, remove the explosion point to free up space
                this.booms.splice(bnum, 1)}}if(this.timerNum >= this.timerTarget){
            // The explosion effect is instantiated when the threshold is reached
            this.pushBoom()
            this.timerNum = 0
        }else{
            this.timerNum ++ 
        }
    }
}

let a = new Animate()
a.run()
Copy the code

Part four – Merge code, from one to more

Merging code is mostly a matter of order. On the site, the coordinate point of the scintillation circle is the target end point of the ray, but also the coordinate starting point of the explosion effect. In terms of time, the detonation method can be triggered after the and ray reach the end point.

let canvas = document.querySelector('#canvas')
let context = canvas.getContext('2d')
let cw = canvas.width = window.innerWidth
let ch = canvas.height = window.innerHeight

function randomColor(){
    // Return a value between 0 and 255, three randomly combined to locate an RGB color
    let num = 3
    let color = []
    while(num--){
        color.push(Math.floor(Math.random()*254+1))}return color.join(', ')}class Kirakira {
    constructor(targetX, targetY){
        // Specify the generated coordinate points
        this.targetLocation = {x: targetX, y: targetY}
        this.radius = 1
    }
    draw() {
        // Draw a circle
        context.beginPath()
        context.arc(this.targetLocation.x, this.targetLocation.y, this.radius, 0.Math.PI * 2)
        context.lineWidth = 2
        context.strokeStyle = `rgba(${randomColor()}`, 1);
        context.stroke()
    }

    update(){
        // Let the circle expand to achieve the flicker effect
        if(this.radius < 5) {this.radius += 0.3
        }else{
            this.radius = 1
        }
    }

    init() {
        this.draw()
        this.update()
    }
}

class Biubiubiu {
    constructor(startX, startY, targetX, targetY) {
        this.startLocation = {x: startX, y: startY}
        this.targetLocation = {x: targetX, y: targetY}
        // Move the current coordinate. The initial default is the starting coordinate
        this.nowLoaction = {x: startX, y: startY}
        // The distance to the target
        this.targetDistance = this.getDistance(this.startLocation.x, this.startLocation.y, this.targetLocation.x, this.targetLocation.y);
        / / speed
        this.speed = 2
        / / acceleration
        this.acceleration = 1.02
        / / Angle
        this.angle = Math.atan2(this.targetLocation.y - this.startLocation.y, this.targetLocation.x - this.startLocation.x)
        
        // Line segment set
        this.collection = []
        // Set the line segment to 10 frames
        this.collection = new Array(CONFIG.biuCollectionCont)
        // Whether the target point is reached
        this.arrived = false
    }

    draw() {
        context.beginPath()
        try{
            context.moveTo(this.collection[0] [0].this.collection[0] [1])}catch(e){
            context.moveTo(this.nowLoaction.x, this.nowLoaction.y)
        }
        context.lineWidth = 3
        context.lineCap = 'round'
        context.lineTo(this.nowLoaction.x, this.nowLoaction.y)
        context.strokeStyle = `rgba(${randomColor()}`, 1);
        context.stroke()                                
    }

    update() {
        this.collection.shift()
        this.collection.push([this.nowLoaction.x, this.nowLoaction.y])
        this.speed *= this.acceleration
        let vx = Math.cos(this.angle) * this.speed
        let vy = Math.sin(this.angle) * this.speed
        let nowDistance = this.getDistance(this.startLocation.x, this.startLocation.y, this.nowLoaction.x+vx, this.nowLoaction.y+vy)
        if(nowDistance >= this.targetDistance){
            this.arrived = true
        }else{
            this.nowLoaction.x += vx
            this.nowLoaction.y += vy
            this.arrived = false
        }
    }

    getDistance(x0, y0, x1, y1) {
        // Calculate the distance between two coordinate points
        let locX = x1 - x0
        let locY = y1 - y0
        // Pythagorean theorem
        return Math.sqrt(Math.pow(locX, 2) + Math.pow(locY, 2))
    }

    init() {
        this.draw()
        this.update()
    }
}

class Boom {
    // Explosives have no definite end point coordinates, which can be defined by setting a certain threshold
    constructor(startX, startY){
        this.startLocation = {x: startX, y: startY}
        this.nowLocation = {x: startX, y: startY}
        / / speed
        this.speed = Math.random()*10+2
        / / acceleration
        this.acceleration = 0.95
        // There is no definite end point, so there is no fixed Angle, can be random Angle diffusion
        this.angle = Math.random()*Math.PI*2
        // Set the threshold to 100
        this.targetCount = 100
        // Currently calculated as 1 to determine whether the threshold will be exceeded
        this.nowNum = 1
        / / transparency
        this.alpha = 1
        // Transparency reduces the gradient
        this.grads = 0.015
        // Gravity coefficient
        this.gravity = 0.98
        
        // Set the line segment to 10 frames
        this.collection = new Array(10)
        
        // Whether the target point is reached
        this.arrived = false
    }

    draw(){
        context.beginPath()
        try{
            context.moveTo(this.collection[0] [0].this.collection[0] [1])}catch(e){
            context.moveTo(this.nowLoaction.x, this.nowLoaction.y)
        }
        context.lineWidth = 3
        context.lineCap = 'round'
        context.lineTo(this.nowLocation.x, this.nowLocation.y)
        // Set the fade effect caused by transparency reduction to look less abrupt
        context.strokeStyle = `rgba(${randomColor()}.The ${this.alpha}) `
        context.stroke()
    }

    update(){
        this.collection.shift()
        this.collection.push([this.nowLocation.x, this.nowLocation.y])
        this.speed *= this.acceleration
        
        let vx = Math.cos(this.angle) * this.speed
        // Add the coefficient of gravity, and the trajectory tends downward
        let vy = Math.sin(this.angle) * this.speed + this.gravity

        // When the current calculation is greater than the threshold, the fading process begins
        if(this.nowNum >= this.targetCount){
            this.alpha -= this.grads
        }else{
            this.nowLocation.x += vx
            this.nowLocation.y += vy
            this.nowNum++
        }

        // If the transparency is 0, it can be removed to free up space
        if(this.alpha <= 0) {this.arrived = true
        }
    }

    init(){
        this.draw()
        this.update()
    }
}

class Animate {
    constructor() {// Used to record the coordinates of the current instantiation
        this.startX = null
        this.startY = null
        this.targetX = null
        this.targetY = null
        // Define an array as a collection of scintillation balls
        this.kiras = []
        // Define an array as a collection of ray classes
        this.bius = []
        // Define an array as a collection of explosion classes
        this.booms = []
        // To avoid excessive drawing on every frame, set the threshold and draw when the threshold is reached
        this.timerTarget = 80
        this.timerNum = 0
    }

    pushBoom(x, y){
        // Instantiate explosion effect, random number of ray diffusion
        for(let bi = Math.random()*10+20; bi>0; bi--){
            this.booms.push(new Boom(x, y))
        }
    }

    run() {
        window.requestAnimationFrame(this.run.bind(this))
        context.clearRect(0.0, cw, ch)
        
        let biuNum = this.bius.length
        while(biuNum-- ){
            this.bius[biuNum].init()
            this.kiras[biuNum].init()
            if(this.bius[biuNum].arrived){
                // After reaching the target, you can start drawing the explosion effect. The target point of the current line is the starting point of the explosion instance
                this.pushBoom(this.bius[biuNum].nowLoaction.x, this.bius[biuNum].nowLoaction.y)

                // After reaching the target, remove the current class to free up space
                this.bius.splice(biuNum, 1)
                this.kiras.splice(biuNum, 1)}}let bnum = this.booms.length
        while(bnum--){
            // Trigger animation
            this.booms[bnum].init()
            if(this.booms[bnum].arrived){
                // After reaching the target transparency, remove the explosion point to free up space
                this.booms.splice(bnum, 1)}}if(this.timerNum >= this.timerTarget){
            // Start drawing the instantiation ray when the threshold is reached
            this.startX = Math.random()*(cw/2)
            this.startY = ch
            this.targetX = Math.random()*cw
            this.targetY = Math.random()*(ch/2)
            let exBiu = new Biubiubiu(this.startX, this.startY, this.targetX, this.targetY)
            let exKira = new Kirakira(this.targetX, this.targetY)
            this.bius.push(exBiu)
            this.kiras.push(exKira)
            // Reset the current count when it reaches the threshold
            this.timerNum = 0
        }else{
            this.timerNum ++ 
        }
    }
}

let a = new Animate()
a.run()
Copy the code

The more playful effect derived from the production process

  1. codepen
  2. codepen