theme: scrolls-light
highlight: atom-one-dark
介绍
大家好,我是爱吃鱼的桶哥Z,很久没有写关于 canvas
效果的文章了,刚好最近又学到了一个新的特效,使用 canvas
绘制多层次动态星空背景,今天就分享给大家。首先我们依旧来看一下最终实现的效果,如图所示:
由于录制 GIF
造成失帧,因此图片可能看不出完整的动画,完整的效果及代码可以拉到文章最底部来进行观赏。
在上图中可以简单的看出有层次的星空图在不断的变换,一会儿向右移动,一会儿又向左移动,最后还会进行旋转,那么这样的效果是如何实现的呢?咱们就一起来学习一下吧!
绘制动态星空图
因为这个效果是使用 canvas
来实现的,因此咱们就需要做一些基础的准备工作,由于这次是做背景效果,因此不需要在 html
中添加默认的标签,我们会通过代码来动态插入 canvas
标签。
首先还是需要准备好相关的 css
样式代码,涉及到的样式很简单,如下所示:
*{
margin: 0;
padding: 0;
}
body {
background: #000;
overflow: hidden;
}
简单的设置好 css
代码后,接下来就需要添加基础的 TS
准备代码了同鞋悉的童鞋们可能都知道这里为啥要使用 TS
了吧,如果还不清楚的,可以去查看我之前写的文章,这里咱们依旧使用 TS + ES6
的语法来进行编写,基础的准备代码如下所示:
class StarrySky {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
constructor() {
this.canvas = document.createElement('canvas') as HTMLCanvasElement;
this.canvas.width = innerWidth;
this.canvas.height = innerHeight;
this.canvas.style.zIndex = '-1';
this.ctx = this.canvas.getContext('2d');
document.body.appendChild(this.canvas);
}
}
在基础的准备代码中,我们通过 document.createElement
创建了一个 canvas
标签,并给它设置了相关的宽高和层级,最后通过 document.body.appendChild
将 canvas
插入到 body
中。有了以上准备代码后,接下来咱们先再把动画相关的代码也编写好,代码如下所示:
class StarrySky {
...other code
constructor () {
...other code
}
draw() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
animate() {
requestAnimationFrame(() => this.animate());
this.draw();
}
}
其实现在 canvas
上面已经有了动画了,只是因为还没有绘制内容,因此看不到效果,接下来咱们就该添加星空效果了。
星空效果其实就是很多不同的粒子展示在 canvas
中,因此咱们需要先创建一个 Particle
粒子类,然后通过生成N个粒子,从而在 canvas
中展示,一起来看一下 Particle
粒子类的相关代码吧,如下:
class Particle {
x: number;
y: number;
vx: number;
w: number;
h: number;
ctx: CanvasRenderingContext2D;
constructor(width: number, height: number, ctx: CanvasRenderingContext2D) {
this.w = width;
this.h = height;
this.ctx = ctx;
this.x = Math.random() * width;
this.y = Math.random() * height;
this.vx = Math.random();
}
update() {
this.x += this.vx * 3;
if (this.x > this.w) {
this.x = 0;
}
}
draw() {
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, 1 + this.vx, 0, Math.PI * 2);
this.ctx.fillStyle = `rgba(255, 255, 255, ${this.vx})`;
this.ctx.fill();
}
}
在 Particle
粒子类中,我们添加了三个随机值,并在 update
方法中不断更新 x
值的数值,在前面的文章中讲过,我们要改变某个元素的位置时,只需要改变它沿 x轴 或 y轴 的属性值即可,因此这里也是一样的;而在 draw
方法中,我们通过 ctx.arc()
方法来绘制每一个粒子圆,并通过 fillStyle
设置粒子的颜色,当 this.vx
值越小时,这个粒子的透明度越低,这样看起来就会显得非常有层次感。
有了 Particle
粒子类,接下来咱们就需要在前面的 StarrySky
类中进行使用了。首先咱们还要对 StarrySky
类中的 constructor
添加更多的属性,代码如下:
class StarrySky {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
particles: Particle[];
count: number;
actions: string[];
action: number;
constructor() {
...other code
this.particles = [];
this.count = 1000;
this.animate();
}
}
可以看到我们在 constructor
中新增了一个 particles
数组,它主要用于存储生成的粒子类,然后我们需要在 StarrySky
类的 draw
方法中生成粒子,一起来看代码:
class StarrySky {
...other code
draw() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (this.particles.length < this.count) {
this.particles.push(
new Particle(this.canvas.width, this.canvas.height, this.ctx)
);
}
for (let i in this.particles) {
const p = this.particles[i];
p.update();
p.draw();
}
}
}
当我们在 StarrySky
类的 draw
方法中动态生成粒子,并调用对应的 update
和 draw
方法时,最终可以看到如下效果:
通过上图咱们可以看到一幅有层次的星空图已经绘制回来了,但是目前的星空只会向右进行移动,效果还是有些单调,因此咱们接下来通过代码实现点击任何区域让星空的移动能够变换方向,那么该如何实现呢?
变幻的星空图
在前面咱们已经通过代码实现了有层次的星空背景绘制了,但是目前可以看到星空的移动只有一个方法,那么该如何修改星空的移动方法呢?还记得在 Particle
星空类 中的 update
方法吗?在 update
方法中,我们是通过修改当前 this.x
的值从而让粒子实现移动的,因此咱们可以从 update
方法入手,代码如下:
class Particle {
...other code
update(direction = 'right') {
switch (direction) {
case 'right':
this.x += this.vx * 3;
if (this.x > this.w) this.x = 0;
break;
case 'left':
this.x -= this.vx * 3;
if (this.x < 0) this.x = this.w;
break;
case 'up':
this.y -= this.vx * 3;
if (this.y < 0) this.y = this.h;
break;
case 'down':
this.y += this.vx * 3;
if (this.y > this.h) this.y = 0;
break;
}
}
}
通过修改 Particle
类的 update
方法可以看出,我们通过一个变量来判断当前移动的方向,并且设置每个方法不同的值,接下来我们还需要继续修改 StarrySky
类,这样咱们才能实现星空方法的不断变换,一起来看代码:
class StarrySky {
...other code
actions: string[];
action: number;
constructor() {
...other code
this.actions = ['right', 'left', 'up', 'down', 'around'];
this.action = 0;
this.animate();
this.event();
}
event() {
document.body.addEventListener('click', () => {
this.action++;
this.action = this.action % this.actions.length;
});
}
draw() {
...other code
for (let i in this.particles) {
const p = this.particles[i];
p.update(this.actions[this.action]);
p.draw();
}
}
}
在 StarrySky
类的 constructor
方法中,我们添加了一个 actions
数组,里面包含了上下左右以及旋转五个值,然后还添加了一个 action
变量,用于表示当前是第几个,并且通过 document.body.addEventListener
给 body
添加了一个 click
事件,当 body
被点击时,修改当前的 action
,从而改变星空的移动方向。最后在 StarrySky
类的 draw
方法中,调用 Particle
类的 update
方法时,将当前的移动方向传入即可实现改变星空方向变换的效果了。
当然,咱们还剩下最后一步,还记得上面的 actions
中咱们总共写了五个值,但是在 Particle
类的 update
方法中,咱们只添加了四个 case
,因此还剩下最后一个旋转的 case
需要编写,代码如下:
class Particle {
...other code
update(direction = 'right') {
switch (direction) {
...other code
case 'around':
let deg = Math.atan2(
this.y - this.h / 2,
this.x - this.w / 2
);
let r = Math.sqrt(
Math.pow(this.x - this.w / 2, 2) + Math.pow(this.y - this.h / 2, 2)
)
this.x = r * Math.cos(deg + this.vx / 200) + this.w / 2;
this.y = r * Math.sin(deg + this.vx / 200) + this.h / 2;
break;
}
}
}
当 case
为 around
时,星空需要变换为渲染的形式,咱们就需要找到旋转的角度和半径,根据勾股定理和三角函数相关的知识,最终获取到当前移动的 x
和 y
的值。完整的代码及实现效果如下所示:代码片段
总结
总的来说,这个效果还是很不错的,并且还添加了相关的交互事件,实现的原理也很简单,有兴趣的童靴可以自己实现一下,本期的内容到此结束。
最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家
本文正在参加「金石计划 . 瓜分6万现金大奖」
暂无评论内容