Mid-Autumn Festival-canvas-beautiful starry sky

When will the bright moon appear? Ask the sky for wine. I don’t know what year it is today in the palace in the sky. I want to ride the wind back home, but I am afraid that it will be cold in the high places. Dancing clear shadow, like in the world.
Turn to the Zhu Pavilion, look down at the Qihu, and the light will make you sleepless. There shouldn't be any hatred, so what's the point of saying goodbye? People have joys and sorrows, separations and reunions, and the moon waxes and wanes. This is a difficult thing in ancient times. Nung, moon and new moon.
On the occasion of the Mid-Autumn Festival, I just made a starry sky background and would like to share it here. The renderings are as follows:
PC end effect:
Insert image description here
Mobile end effect:
Insert image description here
Skill point
html and canvasApi are used to operate canvas for painting.
The class keyword in es6 simply encapsulates key blocks to improve reusability.

Requirements analysis
The final rendering is shown in the picture above:
the foreground is the mountain, the moon and the text, the middle ground is the starry sky, and the background is a gradient color as the sky.
The stars move from right to left, and have a twinkling effect to enhance the user experience.

**PS:** The text here should have been loaded with other fonts, or should have emerged one by one when loading for the first time. One is because of laziness, and the other is because the UI level is really poor, and I think this looks pretty good. I'm too lazy to do it. If you are interested, you can optimize it yourself. [Manual silly laugh]
Idea analysis
The requirements are not troublesome. You can complete it after working with canvas for a while. Because it is a demo work, I try to use as many bells and whistles as possible. As the saying goes, “If you don’t pretend now, then when will you wait?”
In fact, there are not too many bells and whistles!
First of all, I used the module method to introduce the es6 standard package in the default HTML, and used the new keyword to directly draw the entire project. The main reason here is to consider that in the webpack environment, the project can be fine-tuned and packaged using webpack. into an independent js package for sharing.
Then, several core elements mentioned in the requirements: canvas, moon (moon), star (star) and the entire project moon-night are independently packaged into classes to improve the reusability of the code, and they can be proposed separately to other It is used directly in the local area. Of course, this is a story later.
Finally, a simple constraint was made on all types of classes. Each class has a private method of init, draw, and update, which correspond to the three states of initialization, painting, and update respectively, or as I called it, three life cycle functions. , benchmarking vue’s declaration cycle function. [Arrogant]
In summary, this is the idea of ​​the entire project. The following is the code analysis and implementation process.
Implementation
0. Project structure
shell copy code moon-night
|–index.html
|–index.js
|–canvas.class.js
|–star.class.js
|–moon.class.js

1. index.html
index.html is very simple, almost no declarations are needed, just clear the default margin and padding of the canvas, otherwise there will be a white border.
You can see the type = "module" in the script. In this pair of script tags, you can use import to import the package exported by es6 syntax.
htmlcopy code

温馨夜色

– The page above was generated with one click using vscode. Whether the viewport configuration and http-equiv inside it does not matter to this project.

2. canvas.class.js
canvas.class.js, after seeing my naming, you should know what kind of file this is.
Yes, that's what you think.
A class is declared with the name Canvas. The Canvas class can be used as the base class for all related canvas projects in the future. Canvas and canvas.getContext('2d') are declared internally to be inherited in subsequent real drawing classes to ensure that all subclasses operate on a canvas. .
At the same time, a listening function is bound to reinitialize the width and height of the canvas after the browser window changes. This is also a method often used in canvas.
The code is very simple, as follows:
js copy code export default class Canvas { // Receives two parameters, id and config// Currently config only receives one attribute screen, which is used to control whether to follow window changesconstructor(id, config) {


if (id !== 0 && !id) {
  throw new Error('id 不能为空!')
}

this.cv = document.getElementById(id)
this.ctx = this.cv.getContext('2d')

this.config = Object.assign({
  isScreen: true
}, config)

this.initSize()

}

initSize() { this.cv.style.display = “block” // Read the value of the incoming isScreen, and determine whether to mount the resize method based on the value if (this.config.isScreen) { this.cv.width = window. innerWidth this.cv.height = window.innerHeight window.addEventListener("resize", this.resizeCanvas.bind(this), false) } else { this.cv.width = this.cv.parentNode.offsetWidth this.cv.height = this.cv.parentNode.offsetHeight }









}

resizeCanvas() { // Re-declare the width and height of the canvas this.cv.width = window.innerWidth this.cv.height = window.innerHeight // The draw method in the subclass this.draw() }





}

The key point is to talk about the this.draw() method in resizeCanvas. The point of this here will change after the canvas is inherited, that is, it will be consistent with the point of this in the subclass. Therefore, the init method is written as initSize to avoid init being rewritten after inheritance.

3. index.js (moonNight.class.js)
index.js is actually moonNight.class.js. Combined with the canvas file above, this file finally exports a default class: MoonNight.
MoonNight inherits from Canvas, so cv and ctx can be used directly, and all items in the project are implemented in this class.
In addition to the three basic methods mentioned at the beginning, MoonNight also declares two methods, drawing text, drawing sky, and drawing mountains. And the methods are all executed in draw.
There is a little knowledge point here, that is, in the canvas, when the things drawn later overlap in the canvas, the things drawn later will automatically cover the elements drawn earlier. The same principle is similar to the z-index attribute of CSS. Therefore, we should pay attention to the execution order of each method in draw. It should be combined with the renderings and drawn from the bottom up.
js copy code "use script"

//Introduce each package
import Canvas from “./canvas.class.js”
import Star from ‘./star.class.js’
import Moon from “./moon.class.js”

// Export the MoonNight class by default and inherit from Canvas
export default class MoonNight extends Canvas { constructor(id, config) { // super inherits the parent class super(id, config)


// config 为传入的参数,这里还没有什么用,只有传入 canvas 的 isScreen
this.baseConf = Object.assign({ isScreen: true }, config)

// 声明 moon 成员
this.moon = null

// 填充一个随机数组成的 星星 列表
// 星星的数量为 宽度的五分之一
this.starPoints = Array(parseInt(this.cv.width / 5)).fill({}).map(item => {
  return {
    x: parseInt(Math.random() * this.cv.width), // 星星的坐标 x
    y: parseInt(Math.random() * this.cv.height * .6), // 星星的坐标 y
    size: parseInt(Math.random() * 10 + 5), // 星星的大小
    big: Math.random() > .5 ? true : false // 一个Boolean值,表示星星下一次变大还是变小
  }
})

// 初始化 MoonNight,并开始绑定 动画
this.init()
this.t = setInterval(this.update.bind(this), 1000 / 30)

}

// init method
init() { console.log('MoonNight::: init!'); this.draw() }


// Drawing method
draw() { // Draw each element one by one this.drawSky() // Draw stars and convert each coordinate into an instantiated star this.stars = this.starPoints.map(item => new Star (this.ctx, item)) this.drawMountain() // Copy moon to Moon object this.moon = new Moon(this.cv, this.ctx) this.drawText() }








// Draw text
drawText() { let fontSize = this.cv.width > this.cv.height ? this.cv.width * .02 : this.cv.height * .02 console.log('MoonNight::: drawText !'); this.ctx.beginPath() this.ctx.font = ; this.ctx.fillStyle = "#F0F003"; this.ctx.textAlign = "center"; this.ctx.textBaseline = "middle"; / / Draw text (parameters: the word to be written, x coordinate, y coordinate) this.ctx.fillText("I hope people will live long,", this.cv.width * .7, this.cv.height * .16); this.ctx.fillText("We are thousands of miles apart from each other.", this.cv.width * .7, this.cv.height * .16 + fontSize + 10); this.ctx.closePath() }



${fontSize}px bold 楷体







// 画 天空

drawSky() {
console.log(‘MoonNight::: drawSky!’);
let grad = this.ctx.createLinearGradient(0, 0, 0, this.cv.height)
grad.addColorStop(0, “#011447”);
grad.addColorStop(.2, “#011447”);
grad.addColorStop(1, “#958E78”);
this.ctx.fillStyle = grad;
this.ctx.fillRect(0, 0, this.cv.width, this.cv.height);
this.ctx.fill()
}

// 画 山
drawMountain() {
// 山 各个点
let points = [
[0, this.cv.height * (.7)],
[this.cv.width * .06, this.cv.height * .6],
[this.cv.width * .09, this.cv.height * .66],
[this.cv.width * .16, this.cv.height * .5],
[this.cv.width * .2, this.cv.height * .6],
[this.cv.width * .22, this.cv.height * .57],
[this.cv.width * .4, this.cv.height * .8],
[this.cv.width * .5, this.cv.height * .7],
[this.cv.width * .6, this.cv.height * .8],
[this.cv.width * .75, this.cv.height * .75],
[this.cv.width * .9, this.cv.height * .8],
[this.cv.width * .95, this.cv.height * .75],
[this.cv.width, this.cv.height * .8],
[this.cv.width, this.cv.height]
]

// 山 阴影
// 阴影 为山的点进行转换
let yy = points.map(item => {
  return [
    item[0] + 8, item[1] - 1
  ]
})

// 先画 阴影,使用画布默认机制
this.ctx.beginPath()
this.ctx.fillStyle = 'pink'
this.ctx.moveTo(0, this.cv.height)
for (let item of yy) {
  this.ctx.lineTo(item[0], item[1])
}
this.ctx.fill()
this.ctx.closePath()

// 画山
this.ctx.beginPath()
this.ctx.fillStyle = '#1A1401'
this.ctx.moveTo(0, this.cv.height)
for (let item of points) {
  this.ctx.lineTo(item[0], item[1])
}
this.ctx.fill()
this.ctx.closePath()

}

// Update method
update() { console.log('MoonNight::: update!'); // Clear the canvas this.ctx.clearRect(0, 0, this.cv.width, this.cv.height) this. starPoints = this.starPoints.map(item => { return { // Update the x position. If it goes to the far left, it will directly change to the far right. If converted to a random number, subsequent coordinates will cluster like the left half x: item.x - 1 < 0 ? this.cv.width : item.x - 1, y: item.y, // Update size, the step value is .5 size: item.big ? item.size + .5 : item.size - . 5, // If the star size exceeds the maximum value, it starts to get smaller, otherwise it starts to get bigger big: item.size > 15 && item.big === true ? false : item.size < 1 && item.big === false ? true : item.big } }) // After the data is updated, redraw this.draw() } }

















4. star.class.js
Because the calculation of the coordinates of a five-pointed star is relatively complicated, in order to draw the star quickly, a four-pointed star is selected.
As can be seen from the above, drawing stars requires three key values: x, y, size, and a big attribute as the basis for update.
The coordinates of the stars are as follows:
Insert image description here
the soul painter is online, as shown in the picture above, through the incoming x, y, size, 8 vertices can be sorted out, and a buling buling can be obtained by connecting and filling the 8 vertices. of small stars, and modify the size at the same time to achieve the effect of twinkling stars.
The code is as follows:
js copy code export default class Star { constructor(ctx, config) { this.ctx = ctxthis.config = Object.assign({ x: 100, y: 100, size: 10 }, config)


this.init()

}

init() {
this.draw()
}

draw() {
this.x = this.config.x
this.y = this.config.y

	// 0.6 为星星的宽高比酸楚,计算得出宽高
this.w = this.config.size * .6
this.h = this.config.size

// 0.05 为中心点到最近的四个点的坐标差值百分比
this.i = this.config.size * .05

// 导出 八个点
this.points = [
  [this.x - this.w / 2, this.y],
  [this.x - this.i, this.y - this.i],

  [this.x, this.y - this.h / 2],
  [this.x + this.i, this.y - this.i],

  [this.x + this.w / 2, this.y],
  [this.x + this.i, this.y + this.i],

  [this.x, this.y + this.h / 2],
  [this.x - this.i, this.y + this.i]
]

// 白色的小星星就是这么画出来的
this.ctx.beginPath()
this.ctx.fillStyle = '#fff'
this.ctx.moveTo(this.points[0][0], this.points[0][1])
for (let point of this.points) {
  this.ctx.lineTo(point[0], point[1])
}
this.ctx.fill()
this.ctx.closePath()

}

// The update method is actually useless. Let’s explain
update() { this.config = { x: this.config.x–, y: this.config.y, size: this.config.size } this.draw () } }







Explanation:
In this project, the update of stars should actually be declared in the update of star, but the update method of MoonNight is uninstalled in this project.
why?
Because I personally think that directly operating the array in MoonNight may be faster than traversing each star object. It is purely intuitive and has not been tested. If you are interested, you can try it out.
Hahaha~~~

5. moon.class.js
The moon is very simple, because the moon does not move in this project. If it does, the halo will probably flash.
So there is no update method in this class, only draw.
After reading the above code, this is almost not difficult:
js copy code export default class Moon { constructor(cv, ctx) { this.cv = cvthis.ctx = ctx


this.init()

}

init() {
this.draw()
}

draw() { let r = this.cv.width > this.cv.height ? this.cv.width * .05 : this.cv.height * .05 this.ctx.beginPath() this.ctx.fillStyle = ' #F0F003' this.ctx.shadowOffsetX = 0; // Shadow Y-axis offset this.ctx.shadowOffsetY = 0; // Shadow X-axis offset this.ctx.shadowBlur = 14; // Blur size this.ctx.shadowColor = '#F0F003'; // Color






this.ctx.arc(this.cv.width * .2, this.cv.height * .2, r, 0, 2 * Math.PI)
this.ctx.fill()
// 把 阴影设为0,避免影响到其他地方
this.ctx.shadowOffsetX = 0; // 阴影Y轴偏移
this.ctx.shadowOffsetY = 0; // 阴影X轴偏移
this.ctx.shadowBlur = 0;
this.ctx.closePath()

this.ctx.beginPath()
this.ctx.strokeStyle = '#F2D90B'
this.ctx.lineWidth = r * .15
this.ctx.lineCap = 'round'

this.ctx.arc(
  this.cv.width * .2,
  this.cv.height * .2,
  r * .8,
  15 / 360 * Math.PI,
  170 / 360 * Math.PI);
this.ctx.stroke()
this.ctx.closePath()

}
}

To sum up,
after seeing this, do you think it is very simple?
This demo is very simple in nature. I mainly want to use classes to encapsulate some examples in the process of using canvas, so I split a js file into four and reference each other. Obviously, you can also use mountains and text. , the sky are all lifted out, and then assembled. In the same way, you can encapsulate elements such as houses, trees, clouds, etc., and bind some interaction methods (clicks, buttons) to the elements to achieve other needs.
Canvas may seem confusing to those who are new to it. In fact, it is very interesting to study it. Especially when you are studying algorithms, you can use very clever methods to show every step of the algorithm implementation process. I will stick to that very Great design! Breathtaking!

Guess you like

Origin blog.csdn.net/longxiaobao123/article/details/132975426