这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
前言
水是最好的。
——泰勒斯
介绍
我们打开网站或者游戏避免不了的就是就是加载,在这枯燥乏味的等待中,人们会无所适从,如果时间太久那更是一种折磨,我们现在唯一能做的或许就是让他们心态稍平和些,努力的等待任务加载完成。就是这样,加载动画诞生了。我们今天做的就是一类加载动画——波浪球。这个十分常见,有的是拿css3画两个圆去做障眼法实现,有的是利用svg路径动画实现。但今天我们要做的是纯canvas api来实现他。
这个例子很简单,我们将从基础结构,波浪球类,波浪动画来讲解。准备好了么?我们出发了!!
出发
1.基础结构
<canvas id="canvas"></canvas>
<script type="module" src="./app.js"></script>
这里把我们的画布方式去,然后再利用module模式引入js方便其模块导入。
* {
padding: 0;
margin: 0;
}
html,
body {
width: 100%;
height: 100vh;
overflow: hidden;
}
body {
background-image: repeating-radial-gradient(circle at center center, transparent 0px, transparent 8px, rgba(255, 255, 255, 0.05) 8px, rgba(255, 255, 255, 0.05) 11px, transparent 11px, transparent 17px, rgba(255, 255, 255, 0.05) 17px, rgba(255, 255, 255, 0.05) 25px, transparent 25px, transparent 38px, rgba(255, 255, 255, 0.05) 38px, rgba(255, 255, 255, 0.05) 42px), repeating-radial-gradient(circle at center center, rgb(0, 0, 0) 0px, rgb(0, 0, 0) 11px, rgb(0, 0, 0) 11px, rgb(0, 0, 0) 19px, rgb(0, 0, 0) 19px, rgb(0, 0, 0) 24px, rgb(0, 0, 0) 24px, rgb(0, 0, 0) 33px, rgb(0, 0, 0) 33px, rgb(0, 0, 0) 44px, rgb(0, 0, 0) 44px, rgb(0, 0, 0) 46px);
background-size: 60px 60px;
}
#canvas {
width: 100%;
height: 100%;
}
在css里我们为了背景美观,用repeating-radial-gradient绘制了一张背景图,如下:
/*app.js*/
class Application {
constructor() {
this.canvas = null; // 画布
this.ctx = null; // 环境
this.w = 0; // 画布宽
this.h = 0; // 画布高
this.init();
}
init() {
// 初始化
this.canvas = document.getElementById("canvas");
this.ctx = this.canvas.getContext("2d");
window.addEventListener("resize", this.reset.bind(this));
this.reset();
this.render();
this.step();
}
reset() {
// 屏幕改变
this.w = this.canvas.width = this.ctx.width = window.innerWidth;
this.h = this.canvas.height = this.ctx.height = window.innerHeight;
this.render();
}
render() {
// 主渲染
}
step(delta) {
// 重绘
const { w, h, ctx } = this;
requestAnimationFrame(this.step.bind(this));
ctx.clearRect(0, 0, w, h);
}
}
window.onload = new Application();
我们先把主要结构写好,主要做的操作就是拿到画布,再把画布铺满全屏,就是如此简单。
2.波浪球类
我们什么都不管,先写一个类出来,抽象出一些属性,比如波兰球大小,位置,颜色这些都是很容易想到的,仔细往下想,又会联想到波浪的会有振幅和波频还有既然是加载动画那就有个进度值。
大致就是这么就动手吧。
/*WaveBall.js*/
class WaveBall {
constructor(options) {
this.x = 0; // x轴坐标
this.y = 0; // y轴坐标
this.size = 200; // 大小(直径)
this.color = "rgba(55, 133, 207, .75)"; // 颜色
this.waveWidth = 0.025; // 波频
this.waveHeight = 5.6; // 震幅
this.progress = 0; // 进度
Object.assign(this, options);
this.ctx = null; // 环境
this.startX = 0; // 初始位置
this.offsetX = [0, 0]; // 偏移位置
this.vx = [0.156, 0.097]; // x轴增加量
this.bgColor = this.color; // 背景色
return this;
}
render(ctx) {
this.ctx = ctx;
const {color,size} = this;
this.bgColor = ctx.createLinearGradient(size / 2, size / 2, size / 2, size);
this.bgColor.addColorStop(0, color);
this.bgColor.addColorStop(1, color);
this.drawBall();
this.drawWave();
return this;
}
drawBall() {}
drawWave() {}
update() {
this.drawWave();
this.drawBall();
}
}
export default WaveBall;
基础变量基本都有了,我们就开始画个圆,充当球体的外围。
/*WaveBall.js*/
drawBall() {
const { size, ctx, x, y, color } = this;
ctx.save();
ctx.lineWidth = 3;
ctx.strokeStyle = color;
ctx.translate(x, y)
ctx.beginPath();
ctx.arc(0, 0, size / 2, 0, 2 * Math.PI);
ctx.stroke();
ctx.restore();
}
这里不过多赘述,size是直径,所以除以2获得半径这样,会得到一个圆。
正题来了,我们接下来绘制波浪~~
/*WaveBall.js*/
drawWave(n = 0) {
this.offsetX[n] += this.vx[n];
const { startX, size, ctx, offsetX, x, y, bgColor, waveWidth, waveHeight,progress } = this;
let height = -progress/100 * size - 5;
ctx.save();
ctx.translate(x, y)
ctx.beginPath();
ctx.arc(0, 0, size / 2, 0, 2 * Math.PI);
ctx.clip();
ctx.beginPath();
for (let i = - size / 2; i < size / 2; i++) {
const h = waveHeight * Math.sin((startX + i) * waveWidth + offsetX[n]);
ctx.lineTo(i, h+size/2 + height);
}
ctx.lineTo(size / 2, size / 2);
ctx.lineTo(-size / 2, size / 2);
ctx.fillStyle = bgColor;
ctx.fill();
ctx.restore();
}
这里提前要说明一下,因为我们波浪球其实会又两道波浪,由vx变量会影响速度,会使动画出现错峰,增加真实,如果你想的话三四道波浪也是一个逻辑。所以,我们在更新时,要执行两次绘制波浪方法,如下:
/*WaveBall.js*/
update() {
this.drawWave(0);
this.drawWave(1);
this.drawBall();
}
我们绘制整个其实并没有那么复杂,就是我们算x轴要绘制的距离,然后再用三角函数左右他的高低,然后就会出现起伏的画面,然后连接上形成一个矩形,对了别忘了这里还用clip方法去抠出一个圆球来,使之跟刚刚绘制的圆大小一致。就这样,我们就把波浪绘制完了,可是现在还看不了,需要引入到主逻辑中。
3.波浪动画
废话不多说,我们先引进来,先看看长啥样~
/*app.js*/
import WaveBall from "./js/WaveBall.js";
render() {
const { w, h, ctx } = this;
this.waveBall = new WaveBall({
x: w / 2,
y: h / 2,
size: h * 0.45,
progress:50
}).render(ctx);
}
我们为了方面看效果,波浪球实例化设置从进度50开始吧,另外让他在屏幕上居中显示,大小与屏幕高度成正相关。
这样我看还是依然看不到结果,因为我们没有执行他的更新。
最后,我们只要在step重绘阶段不断更新他就可以了。
/*app.js*/
step(delta) {
const { w, h, ctx } = this;
requestAnimationFrame(this.step.bind(this));
ctx.clearRect(0, 0, w, h);
this.waveBall.progress += 0.1;
this.waveBall.progress%= 100;
this.waveBall.update();
}
别忘了,我们模拟了加载进度,所以每次加一点,就行了,满100就让他从头开始~
写到这里,我们的波浪球加载动画就完成了,在线演示
拓展
我们是利用Math.sin实现了这个动画,数学作为工具,让我们可以轻松完成更多的事,当然利用这个还可以做很多事,波浪也可以读取音频去解析,生成可视化音频文件,或将网站滑动过度由矩形面变成波浪面。
弱弱的说一句,贝塞尔曲线也可以画出波浪来,也是一种解决思路哦。
话说,别人加载球真的看烦了,就尝试实现了一下,自己的娃哪里都好,自己的加载球就是百看不厌~~