HTML5 canvas制作动态随机星图

本篇将会介绍如何用canvas制作动态随机移动的星图啦啦啦
demo展示图

小白在远离小白道路上的第一步就是搭个博客

前言

这次的博文不是接着进行博客布置的教学了,对的,我弃坑了,也许以后会回来补
用HMTL5+canvas画东西也很好玩嘛,来来来,我们来画那种会随机动来动去的星图。先展示一个demo

demo访问地址:动态星图demo

准备

在开始这个动态星图的制作前,我希望你能对canvas和它的常用API有一定的了解,在这里给出一份速成教程,四十分钟到一小时可以看完,初步掌握canvas常用API的使用
HTML5- Canvas入门(一)
HTML5- Canvas入门(二)
HTML5- Canvas入门(三)
HTML5- Canvas入门(四)
HTML5- Canvas入门(五)
HTML5- Canvas入门(六)
HTML5- Canvas入门(七)

这一段就叫它正文好了

首先建立html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<title>canvas star demo</title>
<style type="text/css">
body{
text-align: center;
overflow: hidden;
background: #C4C6C8;
}
</style>
</head>
<body>
<canvas id="mycanvas"></canvas>
</body>
</html>

因为我假定你已经有基础或者已经看完了我贴在上面的速成教程,这一段代码就不加以赘述了,接下来是js代码的编写

变量的定义

1
2
3
4
5
6
7
8
9
10
var width = window.innerWidth,  
height = window.innerHeight,
//获取浏览器视窗的尺寸
amount = 50; //出现在画布上的星星个数
var cs = document.getElementById('mycanvas'),//获得canvas元素
cont = cs.getContext('2d'); //对canvas元素进行2d渲染,此后的对画布操作即对cont的操作
cs.width = width;
cs.height = height;
//设置canvas元素的尺寸为浏览器视窗的尺寸
cont.strokeWidth = 1; //设置描边宽度

这一段代码设置了基本的样式和尺寸,接下来就是着手制作星团了

星团对象的定义

要完成星团对象的定义,我们可以先对想要达到的效果进行逐步分析、拆解,再对其内部属性和方法进行不断完善

  1. 先做一个不会动的星团,星团里的所有星星需要一个数组来存储,数组里存储的应该是星星的圆心坐标
  2. 需要有一个画星星的方法,就命名为drawStar好了
  3. 在demo里看到每个星星都连着其他星星,好带感,所以需要一个画线的方法,就将画线的方法命名为drawLine,连线的方法命名为linkStar好了
  4. 每个星星的出现的位置应该是随机的,需要一个产生合适随机数的方法,但是这个方法也许不单单用来产生随机位置,还有随机半径等等,所以就钦定为randomNum
  5. 这里需要一个方法将上面的几个工具方法组合起来,绘制出完整的星图,不是很会起名了,就叫initStars好了,尽管不是很顺口
  6. 以上几个方法足够产生一个静态的随机星图了,要让它动起来的话,还需要一个能够让星图动起来的方法,对的,moveStars

到这里的话,已经初步将对象里需要的方法和属性定义好了,接下来是一步步地实现

属性和各工具方法的定义

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
var Stars = {
starArr: [], //定义星团数组
drawStar: function(cxt,x,y,r){
//cxt为传入的画布对象,(x,y)为星星的圆心坐标,r为星星的半径
cxt.beginPath(); //创建路径
cxt.arc(x,y,r,0,2*Math.PI); //画一个圆
cxt.closePath(); //闭合路径
cxt.fill(); //填充
return{x:x,y:y,r:r}; //返回圆心坐标和半径的对象
},
drawLine: function(cxt,x,y,ex,ey){
//(x,y),(ex,ey)分别为线段的起点和终点坐标
cxt.beginPath();
cxt.strokeStyle = "rgba(0,0,0,1)";//为线段定义描边样式
cxt.moveTo(x,y);
cxt.lineTo(ex,ey);
cxt.closePath();
cxt.stroke(); //描边
},
randomNum: function(max,min){
return Math.floor(Math.random()*(max-min+1)+min);
//完成基本的产生范围内随机数的要求
},
linkStar: function(){
var pStars = this; //创建指针指向调用该方法的Stars对象
for(let i = 0;i < amount;i++){
for(let j = 0;j < amount && i+j < amount;j++){
pStars.drawLine(cont,
pStars.starArr[i+j].x,pStars.starArr[i+j].y,
pStars.starArr[i].x,pStars.starArr[i].y)
}
}
},
...
}

到这一步,我们已经把Stars对象里需要用到的工具方法的定义工作完成了

完成星图的静态模型

到这里,我们再整理一遍这个方法需要完成的功能:组合各个工具函数绘制出静态的星图
代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Stars = {
...
initStars: function(cont){
var pStars = this;
pStars.starArr = []; //初始化为空数组
cont.clearRect(0,0,cs.width,cs.height);//顺手清理画布
for(let i = 0;i < amount;i++){
pStars.starArr.push(pStars.drawStar(cont,
pStars.randomNum(width,0), //随机生成星星的横坐标
pStars.randomNum(height,0), //随机生成星星的纵坐标
pStars.randomNum(2,15) //随机生成星星的半径
));
//返回对象{x:x,y:y,r:r}到starArr
}
pStars.linkStar(); //连线
},
}

按上述步骤简单组织后,效果如图所示
初步效果图
如果你的和我的不一样,那你肯定哪里出问题了。。

美化

这个静态星图的效果很惊悚没错。。需要稍微美化一下。。
首先,在js代码的头部定义变量处加入

1
cont.fillStyle = "rgba(0,0,0,0.05)";      //设置填充样式

接着,修改线段的颜色深浅,我们希望每条线段有不同的深浅度,最好是和线段的长度存在一定关系

  1. 先修改方法drawLine

    1
    2
    3
    4
    5
    6
    7
    8
    9
    drawLine: function(cxt,x,y,ex,ey,o){
    //(x,y),(ex,ey)分别为线段的起点和终点坐标,o为透明度
    cxt.beginPath();
    cxt.strokeStyle = 'rgba(0,0,0,'+o+')';//为线段定义描边样式
    cxt.moveTo(x,y);
    cxt.lineTo(ex,ey);
    cxt.closePath();
    cxt.stroke(); //描边
    }
  2. 修改方法linkStar

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    linkStar: function(){
    var pStars = this; //创建指针指向调用该方法的Stars对象
    for(let i = 0;i < amount;i++){
    for(let j = 0;j < amount && i+j < amount;j++){
    var xx = Math.abs(pStars.starArr[i+j].x - pStars.starArr[i].x),
    yy = Math.abs(pStars.starArr[i+j].y - pStars.starArr[i].y),
    line = Math.sqrt(xx*xx,yy*yy),
    o = 1/line*7-0.009;
    o= o > 0.05 ? 0.05 :o;
    pStars.drawLine(cont,
    pStars.starArr[i+j].x,pStars.starArr[i+j].y,
    pStars.starArr[i].x,pStars.starArr[i].y,o)
    }
    }
    }

修改之后,会呈现如下图效果
静态效果图

完成星图的动态模型

接下来就是制作动态星图的模型了,要实现canvas的动画效果,普遍的做法是在经过一定时间间隔后进行重绘。如此,一个较为简单且容易想到的思路就出现了,经过初始化后的静态星图模型已经将所有星星的的坐标存放于数组starArr中,只需要在清理画布后,将数组内的坐标加上适当偏移后绘制出来,即可达到想要的动态效果。为此,我们需要在对象数组starArr中为对象添加其他属性

1
2
3
4
5
6
7
8
9
10
11
var Stars = {
...
moveStars: function(){
var pStars = this;
for(let i = 0;i < amount;i++){
pStars.starArr[i].moveX = pStars.randomNum(10,-10)/40;
pStars.starArr[i].moveY = pStars.randomNum(10,-10)/40;
}
...
}
}

上面的代码已经为对象数组中的元素添加了新的属性,即偏移量,接下来就是设置定时器使星图重绘了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Stars = {
...
moveStars: function(){
...
setInterval(function(){
cont.clearRect(0,0,cs.width,cs.height); //清理
for(let i = amount - 1;i >= 0;i--){
var star = pStars.starArr[i];
star.x += star.moveX;
star.y += star.moveY;
//完成偏移
star.x < 0 ? (star.x = cs.width) : (star.x > cs.width ? star.x = 0 : '');
star.y < 0 ? (star.y = cs.height) : (star.y > cs.height ? star.y = 0 : '');
Stars.drawStar(cont,star.x,star.y,star.r);
}
Stars.linkStar();
},50)
}
}

至此,就已经将对象的方法和属性全定义完啦,最后一步就是组织js函数调用这个对象

调用

1
2
3
4
window.onload = function(){
Stars.initStars(cont);
Stars.moveStars();
}

组合之后,就能够显示出动态星图的效果啦

美化二

本来想做美化的,做成真正星星那样,但是好麻烦啊,先留着坑,有空再做好了(其实后来作者弃坑了,被繁重的学业压垮了脊梁)

全部代码

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
<!DOCTYPE html>
<html>
<head>
<title>canvas star demo</title>
<style type="text/css">
body {
text-align: center;
overflow: hidden;
background: #C4C6C8;
}
</style>
</head>
<body>
<canvas id="mycanvas"></canvas>
<script type="text/javascript">
var width = window.innerWidth,
height = window.innerHeight,
//获取浏览器视窗的尺寸
amount = 50; //出现在画布上的星星个数
var cs = document.getElementById('mycanvas'), //获得canvas元素
cont = cs.getContext('2d'); //对canvas元素进行2d渲染,此后的对画布操作即对cont的操作
cs.width = width;
cs.height = height;
//设置canvas元素的尺寸为浏览器视窗的尺寸
cont.fillStyle = "rgba(0,0,0,0.15)"; //设置填充样式
cont.strokeWidth = 1; //设置描边宽度
var Stars = {
starArr: [], //定义星团数组
drawStar: function(cxt, x, y, r) {
//cxt为传入的画布对象,(x,y)为星星的圆心坐标,r为星星的半径
cxt.beginPath(); //创建路径
cxt.arc(x, y, r, 0, 2 * Math.PI); //画一个圆
cxt.closePath(); //闭合路径
cxt.fill(); //填充
return {
x: x,
y: y,
r: r
};
//返回圆心坐标的对象
},
drawLine: function(cxt, x, y, ex, ey, o) {
//(x,y),(ex,ey)分别为线段的起点和终点坐标,o为透明度
cxt.beginPath();
cxt.strokeStyle = 'rgba(0,0,0,' + o + ')';//为线段定义描边样式
cxt.moveTo(x, y);
cxt.lineTo(ex, ey);
cxt.closePath();
cxt.stroke(); //描边
},
randomNum: function(max, min) {
return Math.floor(Math.random() * (max - min + 1) + min);
//完成基本的产生范围内随机数的要求
},
linkStar: function() {
var pStars = this; //创建指针指向调用该方法的Stars对象
for (let i = 0; i < amount; i++) {
for (let j = 0; j < amount && i + j < amount; j++) {
var xx = Math.abs(pStars.starArr[i + j].x - pStars.starArr[i].x),
yy = Math.abs(pStars.starArr[i + j].y - pStars.starArr[i].y),
line = Math.sqrt(xx * xx, yy * yy),
o = 1 / line * 7 - 0.009;
o = o > 0.05 ? 0.05 : o;
pStars.drawLine(cont,
pStars.starArr[i + j].x, pStars.starArr[i + j].y,
pStars.starArr[i].x, pStars.starArr[i].y, o)
}
}
},
initStars: function(cont) {
var pStars = this;
pStars.starArr = []; //初始化为空数组
cont.clearRect(0, 0, cs.width, cs.height);//顺手清理画布
for (let i = 0; i < amount; i++) {
pStars.starArr.push(pStars.drawStar(cont, pStars.randomNum(width, 0), pStars.randomNum(height, 0), pStars.randomNum(2, 15)));
//返回对象{x:x,y:y}到starArr
}
pStars.linkStar(); //连线
},
moveStars: function() {
var pStars = this;
for (let i = 0; i < amount; i++) {
pStars.starArr[i].moveX = pStars.randomNum(10, -10) / 40;
pStars.starArr[i].moveY = pStars.randomNum(10, -10) / 40;
}
setInterval(function() {
cont.clearRect(0, 0, cs.width, cs.height);
for (let i = amount - 1; i >= 0; i--) {
var star = this.Stars.starArr[i];
star.x += star.moveX;
star.y += star.moveY;
//完成偏移
star.x < 0 ? (star.x = cs.width) : (star.x > cs.width ? star.x = 0 : '');
star.y < 0 ? (star.y = cs.height) : (star.y > cs.height ? star.y = 0 : '');
Stars.drawStar(cont, star.x, star.y, star.r);
}
Stars.linkStar();
}, 50);
}
}
window.onload = function() {
Stars.initStars(cont);
Stars.moveStars();
}
</script>
</body>
</html>

结语

1
2
3
4
5
6
7
8
9
10
11
12
function Achievement(){
count: 0;
addBlog: function() {
count++;
}
log: function(str) {
console.log(str);
}

var 人生成就 = new Achievement();
人生成就.addBlog();
人生成就.log("前端博客++");
用钱砸我