这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
前言
通向星星的道路是艰难的。
——塞内加
介绍
本期是讲解一个特别容易理解的效果——拖尾+粒子。我们玩仙剑奇侠传三的时候战斗成功界面鼠标移动会出现流光拖尾,或者弹球类游戏小球加速重影拖尾。这种效果第一会给人带来一丝新鲜感划两下,第二会更加清楚自己的鼠标位置。好吧,第二条我是硬凑上去的。不多废话,先看看我们的繁星拖尾是啥样的吧。
是不是有点小激动,本期我们就从结构搭建,星星类,生成星星来展开讲解,好,我们出发~~
出发
1.结构搭建
<canvas id="canvas"></canvas>
<script type="module" src="./app.js"></script>
我们还是放个画布元素,再通过module模式来引入主逻辑,方便后面的模块加载进来。
* {
padding: 0;
margin: 0;
}
html,
body {
width: 100%;
height: 100vh;
position: relative;
overflow: hidden;
}
#canvas {
width: 100%;
height: 100%;
background-image: repeating-radial-gradient(
circle at center center,
transparent 0px,
transparent 6px,
rgba(255, 255, 255, 0.05) 6px,
rgba(255, 255, 255, 0.05) 13px,
transparent 13px,
transparent 16px,
rgba(255, 255, 255, 0.05) 16px,
rgba(255, 255, 255, 0.05) 29px,
transparent 29px,
transparent 40px,
rgba(255, 255, 255, 0.05) 40px,
rgba(255, 255, 255, 0.05) 50px
),
repeating-radial-gradient(
circle at center center,
rgb(21, 21, 21) 0px,
rgb(21, 21, 21) 13px,
rgb(21, 21, 21) 13px,
rgb(21, 21, 21) 14px,
rgb(21, 21, 21) 14px,
rgb(21, 21, 21) 15px,
rgb(21, 21, 21) 15px,
rgb(21, 21, 21) 25px,
rgb(21, 21, 21) 25px,
rgb(21, 21, 21) 37px,
rgb(21, 21, 21) 37px,
rgb(21, 21, 21) 42px
);
background-size: 53px 53px;
}
我们利用css填充满屏幕,然后为了方便观察,绘制一个张背景,毕竟干啥都要有点仪式感。
/*app.js*/
// 后面会写一个星星类,先把引入写好
import Star from "./js/Star.js"
class Application {
constructor() {
this.canvas = null; // 画布
this.ctx = null; // 环境
this.w = 0; // 画布宽
this.h = 0; // 画布高
this.starList = []; // 星星列表
this.starColors = [[246, 241, 82], [216, 124, 156]]; // 星星颜色rgb
this.init();
}
init() {
// 初始化
this.canvas = document.getElementById("canvas");
this.ctx = this.canvas.getContext("2d");
window.addEventListener("resize", this.reset.bind(this));
window.addEventListener("mousemove", this._mouseMove.bind(this), this)
window.addEventListener("touchmove", this._mouseMove.bind(this), this)
this.reset();
this.render();
}
reset() {
// 窗口改变
this.w = this.canvas.width = this.ctx.width = window.innerWidth;
this.h = this.canvas.height = this.ctx.height = window.innerHeight;
}
_mouseMove(e) {
// 输入设备移动事件
}
render() {
// 主逻辑
this.step();
}
drawStars(x, y) {
// 绘制星星
}
step(delta) {
// 重绘
const {ctx, w, h} = this;
requestAnimationFrame(this.step.bind(this));
ctx.clearRect(0, 0, w, h);
}
}
window.onload = new Application();
我们期望的是全屏显示所以窗口改变事件中拿到屏幕的宽高。因为我们要输入设备移动会生成星星,所以我们先把移动事件先监听起来。下面,就是集中精力绘制星星了~
2.星星类
/*Star.js*/
class Star {
constructor(options) {
this.x = 0; // x轴坐标
this.y = 0; // y轴坐标
this.vx = 0; // x轴速度
this.vy = 0; // y轴速度
this.r = 6; // 星星内环
this.R = 12; // 星星外环
this.color = [246, 241, 82]; // 默认颜色
this.scale = 1; // 缩放大小
this.opacity = 1; // 透明度
this.angle = 0; // 角度
this.va = 0; // 角度增量
Object.assign(this, options)
this.active = true; // 是否被激活
return this;
}
render(ctx) {
// 主渲染
if (!ctx)
throw new Error("context is undefined.");
this.ctx = ctx;
this.draw();
return this;
}
draw() {
// 绘制
const {color, scale, x, y, ctx, r, R,opacity,angle,va} = this;
ctx.save();
ctx.translate(x, y);
ctx.scale(scale, scale);
ctx.rotate(angle)
ctx.beginPath();
// 绘制星星
for (let i = 0; i < 5; i++) {
ctx.lineTo(Math.cos((18 + i * 72) / 180 * Math.PI) * R + R,
-Math.sin((18 + i * 72) / 180 * Math.PI) * R + R);
ctx.lineTo(Math.cos((54 + i * 72) / 180 * Math.PI) * r + R,
-Math.sin((54 + i * 72) / 180 * Math.PI) * r + R);
}
ctx.closePath();
ctx.fillStyle = `rgba(${color[0]},${color[1]},${color[2]},${opacity})`;
ctx.fill();
ctx.restore();
this.x += this.vx;
this.y += this.vy;
this.opacity -= .022;
this.angle += va;
if(this.opacity<0){
this.active = false;
}
return this;
}
}
我们这里只讲绘制,到底怎么才能绘制一个五角星呢,其实就是两个圆套起来观察,各个顶点的夹角可以分析出来再用三角函数去计算坐标,再将这些坐标串连起来我的小星星就得到了,具体公式看以上代码。
至于后面的,就是填充颜色了。而且要在每次绘制的时候,改变其位置,这里就用到了vx , vy 两个方向上会有一个速度来让他的位置改变,同时,我们还期望星星绘制后每次角度用va稍微改变一点,还有期望让颜色变淡直至消失,所以要改变他的透明度,让他低于0后失活。这样后面实时绘制的时候,就会动起来~
3.生成星星
/*app.js*/
drawStars(x, y) {
for (let i = 0; i < 5; i++) {
let vx = (Math.random() * 2) - 1;
let vy = (Math.random() * 2) - 1;
const {ctx, starColors} = this;
this.starList.push(new Star({
x,
y,
vx,
vy,
va: Math.random() * 0.01 - 0.02,
scale: Math.random() * 0.5 + 0.5,
color: starColors[~~(Math.random() * starColors.length)]
}).render(ctx))
}
}
我们做的是繁星,所以一次生成多个,这里用for循环一次生成5个吧,而且都要收集到starList里面。然后每生成一个都要随机一个x轴y轴移动速度,角度改变的速度,大小,还有颜色。至于x坐标和y坐标。我们将会通过鼠标移动后的位置来获取。所以,下面我们就可以来写鼠标移动事件了。
_mouseMove(e) {
let x = e.offsetX || (e.changedTouches[0]&&e.changedTouches[0].clientX);
let y = e.offsetY || (e.changedTouches[0]&&e.changedTouches[0].clientY);
this.drawStars(x, y)
}
因为我们还要考虑移动触摸事件,所以触摸时要用到changedTouches。然后再去绘制。
现在如果来看屏幕上还没有东西,因为还没有绘制它,我们要在step事件上实时绘制。
/*app.js*/
step(delta) {
const {ctx, w, h} = this;
requestAnimationFrame(this.step.bind(this));
ctx.clearRect(0, 0, w, h);
this.starList.forEach((star, index) => {
star.draw();
if (!star.active) {
this.starList.splice(index, 1);
}
})
}
我们刚生成的星星都在starList里面存着,所以遍历他,将里面的星星调用绘制方法就完成了,当然我们还要把那些失活消失的星星,从数组中移除,释放内存,不然随之星星实例的增多会极度消耗内存。
写到这里就基本完成了本期内容,这个其实算是一种粒子的基本效果非常简单易学,在线演示
拓展
如果想来个点重力效果也可以在绘制的时候,给y轴速度加个常量如下:
this.vy += 0.1;
this.y += this.vy;
这类效果花样特别多,可以用自己的图片或者形状组合更绚丽的效果。
还有就是,我们做粒子效果避免不了的就是怎么回收他们,我们这里是直接删除再生成新的,当然还可以建立一个程序池来回收,每次生成新的就从池子里拿。
可以用这篇做粒子效果的垫脚石吧,如果有类似的效果,形式也大差不差了,你学废了么~