(十) JavaScript-Canvas 雪花飘(2)

JavaScript 是前端核心, 掌握这门语言是步入前端高手行列必经之路,噢,别忘了还有TypeScript, 学习它还需要OOP知识, 底层的浏览器原理、HTTP协议也必不可少, 此系列文章记录使用JavaScript和Canvas进行游戏开发, 有游戏才有趣!!!

旋转的图形

(十) JavaScript-Canvas 雪花飘(2)

(九) JavaScript-Canvas 雪花飘 中利用两个Canvas绘制了一个图形并在中心旋转,本篇中加入面向对象内容,创建多个图形并旋转,让每个图形旋转的同时向上或向下移动

1.参考 (九) JavaScript-Canvas 雪花飘 这篇完成的代码

  • 先设计两个canvas, 定义他们的样式, 两个canvas都设置为绝对布局,通过z-index设置显示位置
  • 隐藏第一个canvas, display:none
  • canvas1.width = 200 , canvas1.height= 200 表示第一个canvas的宽和高,做成图形后,就是整个图形的大小, 可设计成动态随机大小
  • 第二个画布canvas2的宽度和高度设计为窗口的度和高
  • let color = ‘hsl(‘+Math.random()*100+’,50%,50%)’ 定义一个全局的随机颜色变量,使用hsl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>雪花飘</title>
<style>
#canvas1 {
position:absolute;
top: 0;
left: 0;
background-color: rgb(11, 4, 4);
z-index: 11;
display:none;
}
#canvas2 {
position:absolute;
top: 0;
left: 0;
background: rgb(11, 4, 4);
z-index: -11;
}
</style>
</head>
<body>
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>

<script>
/** @type {HTMLCanvasElement} */
const canvas1 = document.getElementById('canvas1')
const ctx1 = canvas1.getContext('2d')
canvas1.width = 200
canvas1.height= 200
//画布设置
ctx1.lineCap = 'round';
//阴影设置
ctx1.shadowColor = 'rgba(0,0,0,0.7)';
ctx1.shadowOffsetX = 10;
ctx1.shadowOffsetY = 5;
ctx1.shadowBlur = 10;


//第二个画布
const canvas2 = document.getElementById('canvas2');
const ctx2 = canvas2.getContext('2d');
canvas2.width = window.innerWidth;
canvas2.height = window.innerHeight;

let color = 'hsl('+Math.random()*100+',50%,50%)'

</script>
</body>
</html>

2. 定义两个函数用于画线和画图形(由线组成)

  • drawLine为画线函数,线长固定为100(从坐标0,0到100,0)
  • 线的颜色设置为定义的全局color
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function drawLine() {
ctx1.beginPath()
ctx1.moveTo(0,0)
ctx1.lineTo(100, 0)
ctx1.lineWidth=4
ctx1.strokeStyle = color
ctx1.stroke()
}
function draw() {
ctx1.save()
ctx1.translate(canvas1.width/2, canvas1.height/2);
for (let i = 0; i < 12; i++){ //线条数量
ctx1.rotate((Math.PI * 2)/12);//旋转度数 2PI 除以 线条数量
drawLine();//调用画线
}
ctx1.restore()
}
//调用绘图函数
draw()
  • 接下来将canvas1的图形转换为image,使用image的toDataURL()方法
1
2
const image = new Image()
image.src = canvas1.toDataURL();
  • 注册image的加载事件,图形加载完成会触发这个事件
1
2
3
4
//也可以使用addEventListener方式注册事件
image.onload = function() {

}

3. 定义一个Snow形状类

  • 定义Snow的目的是为了创建多个对象
  • 构造函数的定义,属性的定义, 需要搞清楚有哪些属性
  • 属性需要 image 图形;width,height 图形的宽度和高度;x,y图形的坐标; angle旋转的角度;旋转的速度speed等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Snow {
//在调用时传入的是第个canvas的宽度和高度
constructor(canvasWidth, canvasHeight, image){

this.canvasWidth = canvasWidth
this.canvasHeight = canvasHeight
this.image = image
//随机一个size值
this.size = Math.random() * 0.4 + 0.1
//定义图形的宽度和高度
this.width = this.image.width * this.size
this.height = this.image.height * this.size
//图形的x,y坐标必须在画布canvas的宽高范围以内
this.x = Math.random() * this.canvasWidth
this.y = Math.random() * this.canvasHeight
//定义一个速度属性,每个Snow对象的速度都是随机值
this.speed = Math.random() * 1 + 1;

//角度初始值为0
this.angle = 0;
//角度的变化值
this.va = Math.random() * 0.05 - 0.025;
}
}

4. 在Snow类中定义draw和update函数,用于绘图和更新属性

  • update函数主要用于对象属性的重新设置或更改,比如坐标不断更改
  • draw函数用于将图形绘制到画布上, 使用drawImage函数时,图形的位置是当前图形对象的宽高分别除以2,让它以图形的中心点旋转
  • 画图的时候要用上save和restore方法,避免影响到其它Snow对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Snow {
//.....省略了构造函数

update(){
//角度重新赋值
this.angle += this.va;
//判断图形的纵坐标是否超出画布的高度,如果超出高度则重新设置坐标
if (this.y < -this.height) {
this.y = this.canvasHeight + this.height;
this.x = Math.random() * (this.canvasWidth - this.width);
this.angle = 0;
}
else {
//未超出高度则让y的值不断减小,speed的值越大,向上移动的速度越快
this.y -= this.speed;
}
}
//画图
draw(ctx){
ctx.save();
ctx.translate(this.x, this.y );
ctx.rotate(this.angle);
ctx.drawImage(this.image, -this.width/2, -this.height/2, this.width, this.height)
ctx.restore();
}
}

5. 实现动画函数

  • 创建数组snows,存储多个Snow对象
  • 一定要在图形加载完成后再创建snow对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let snows=[]

image.onload = function(){
for (let i = 0; i < 50; i++){
snows.push(new Snow(canvas2.width, canvas2.height, image))
}
function animate() {
//每次清除画布
ctx2.clearRect(0,0,canvas2.width, canvas2.height)
//遍历数组,调用每个对象的draw和update方法
for(let i=0;i<snows.length;i++) {
snows[i].draw(ctx2)
snows[i].update()
}
window.requestAnimationFrame(animate)
}
animate()
}

6. 完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>



#canvas1 {
position:absolute;
top: 0;
left: 0;
background-color: rgb(11, 4, 4);
z-index: 11;
display: none;
}
#canvas2 {
position:absolute;
top: 0;
left: 0;
background: rgb(11, 4, 4);
z-index: -11;
}
</style>
</head>
<body>
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>

<script>
/** @type {HTMLCanvasElement} */
const canvas1 = document.getElementById('canvas1')
const ctx1 = canvas1.getContext('2d')
canvas1.width = 200
canvas1.height= 200
//画布设置
ctx1.lineCap = 'round';
//阴影设置
ctx1.shadowColor = 'rgba(0,0,0,0.7)';
ctx1.shadowOffsetX = 10;
ctx1.shadowOffsetY = 5;
ctx1.shadowBlur = 10;


//第二个画布
const canvas2 = document.getElementById('canvas2');
const ctx2 = canvas2.getContext('2d');
canvas2.width = window.innerWidth;
canvas2.height = window.innerHeight;
let color = 'hsl('+Math.random()*100+',50%,50%)'
function drawLine() {
ctx1.beginPath()
ctx1.moveTo(0,0)
ctx1.lineTo(100, 0)
ctx1.lineWidth=4
ctx1.strokeStyle = color
ctx1.stroke()
}

function draw() {
ctx1.save()
ctx1.translate(canvas1.width/2, canvas1.height/2);
for (let i = 0; i < 12; i++){
ctx1.rotate((Math.PI * 2)/12);
drawLine();
}
ctx1.restore()
}

draw()

const image = new Image()
image.src = canvas1.toDataURL();

class Snow {
constructor(canvasWidth, canvasHeight, image){
this.canvasWidth = canvasWidth
this.canvasHeight = canvasHeight
this.image = image

this.size = Math.random() * 0.4 + 0.1
this.width = this.image.width * this.size
this.height = this.image.height * this.size
this.x = Math.random() * this.canvasWidth
this.y = Math.random() * this.canvasHeight
this.speed = Math.random() * 1 + 1;

this.angle = 0;
this.va = Math.random() * 0.05 - 0.025;

}
update(){
this.angle += this.va;
if (this.y < -this.height) {
this.y = this.canvasHeight + this.height;
this.x = Math.random() * (this.canvasWidth - this.width);
this.angle = 1;
} else this.y -= this.speed;
}
draw(ctx){
ctx.save()
ctx.translate(this.x, this.y)
ctx.rotate(this.angle)
ctx.drawImage(this.image, -this.width/2, -this.height/2, this.width, this.height)
ctx.restore();
}
}

let snows = []

image.onload = function(){
for (let i = 0; i < 50; i++){
snows.push(new Snow(canvas2.width, canvas2.height, image))
}
function animate() {
ctx2.clearRect(0,0,canvas2.width, canvas2.height)
for(let i=0;i<snows.length;i++) {
snows[i].draw(ctx2)
snows[i].update()
}
window.requestAnimationFrame(animate)
}
animate()
}


</script>
</body>
</html>