Today is the Mid-Autumn Festival, so I came up with an idea to draw the moon on canvas.

Thus, a starry sky drawn on canvas was born.

Demo

Here I use ES6 syntax. Stars, moon, and meteors are written as separate modules.

So I divide JS into four files: main.js, moon. js, stars. js and Meteor. Js, and export a class for each of the last three.

The source code

For convenience, gulP is used as an automated tool.

main.js

import Stars from './Stars' import Moon from './Moon' import Meteor from './Meteor' let canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'), width = window.innerWidth, Height = window.innerHeight, // instantiate the moon and stars. Meteors are born at random time, Moon = new moon (CTX, width, height), stars = new stars (CTX, width, height, 200), meteors = [], Width = width canvas.height = height // const meteorGenerator = ()=> { Let x = math.random () * width + 100 meteors.push(new Meteor(CTX, x, height)) // SetTimeout (()=> {meteorGenerator()}, math.random () * 2000)} const frame = ()=> {// Stars flash every 10 frames, Count ++ count % 10 == 0 && stars.blink() moon.draw() stars.draw() meteors.foreach ((meteor, index, meteors) Arr)=> {// If the meteor is out of view, destroy the meteor instance, If (meteor. Flow ()) {meteor. Draw ()} else {arr.splice(index, 1) } }) requestAnimationFrame(frame) } meteorGenerator() frame()Copy the code

At the beginning, another three modules are introduced, which are stars, moons and meteors.

The moon and stars are initialized, but since meteors are generated randomly, an array is initialized to hold the next generation of meteors.

In each frame, draw functions of Moon, Star and meteor are called to draw each frame. In particular, since stars need to blink and meteors need to move, radii and coordinates are processed before DRAW. If a meteor goes out of the canvas, it is cleared from the array, dereferencing and recollecting memory.

Moon.js

export default class Moon { constructor(ctx, width, height) { this.ctx = ctx this.width = width this.height = height } draw() { let ctx = this.ctx, Gradient = CTX. CreateRadialGradient (200, 200, 80, 200, 200, 800) // Radial gradient gradient. 'RGB (255255255)') gradient. AddColorStop (0.01, 'RGB (70,70,80)') gradient. AddColorStop (0.2, 'RGB (40,40,50)') gradient. AddColorStop (0.4, 'RGB (20, 30)) gradient. AddColorStop (1, 'RGB (0,0,10)') ctx.save() ctx.fillstyle = gradient ctx.fillrect (0,0, this.width, this.height) ctx.restore()}}Copy the code

This is the moon class, mainly using the canvas radial gradient effect. In order to achieve harmony, I tried for a long time T_T…

Stars.js

export default class Stars { constructor(ctx, width, height, amount) { this.ctx = ctx this.width = width this.height = height this.stars = this.getStars(amount) } getStars(amount) {  let stars = [] while (amount--) { stars.push({ x: Math.random() * this.width, y: Math.random() * this.height, r: Math.random() + 0.2})} return stars} draw() {let CTX = this.ctx.save () ctx.fillstyle = 'white' this.stars.forEach(star=> { ctx.beginPath() ctx.arc(star.x, star.y, star.r, 0, 2 * math.pi) ctx.fill()}) ctx.restore()} // Blink, Blink () {this.stars = this.stars.map(star=> {let sign = math.random () > 0.5? 1: -1 star.r += sign * 0.2 if (star.r < 0) {star.r = -star.r} else if (star.r > 1) {star.r -= 0.2} return star})}}Copy the code

A collection of stars. Because you don’t want to write each star as a separate object, you write a collection class of stars, all of which are stored in the instance’s stars. The blink function is used to change the radius of each star randomly, so as to produce the effect of flashing.

Meteor.js

export default class Meteor { constructor(ctx, x, h) { this.ctx = ctx this.x = x this.y = 0 this.h = h this.vx = -(4 + Math.random() * 4) this.vy = -this.vx this.len = Math. The random () * 300 + 500} flow () {/ / determine the meteor out if (this. X < - this. Len | | this. Y > this. H + this. Len)} {return false This.x += this.vx this.y += this.vy return true} draw() {let CTX = this.ctx, Gra = ctx.createradialgradient (this.x, this.y, 0, this.x, this.y, This.len) const PI = math.pi gra.addcolorstop (0, 'rgba(255,255,255,1)') gra.addcolorstop (1, 1) 'rgba(0,0,0,0)') ctx.save() ctx.fillstyle = gra ctx.beginpath () Arc (this.x, this.y, 1, PI / 4, 5 * PI / 4) // Draw the meteor tail, Triangle ctx.lineto (this.x + this.len, this.y - this.len) ctx.closepath () ctx.fill() ctx.restore()}}Copy the code

Meteors are more interesting. Guess how each meteor is drawn?

In fact, the outline of each meteor consists of a semicircle and a triangle, similar to a tumbler. Then the overall inclination is 45 degrees, and a radial gradient is used when filling, which is quite perfect for the pop tail to fade away and fade out.

Yeah, that’s how neat it is

Finally, I took a look at the CPU and GPU usage, fortunately, the optimization is still in place, my phone can run smoothly.

Today is Mid-Autumn Festival, but it’s raining here. No moon to see…

But I have this moon.

“I wish each other a long life and share the beauty of this graceful moonlight even though miles apart.” It’s fate to see the same “moon”