canvas - 酷炫粒子文字的代码解析
先看效果:
再放源代码:
点击查看代码
<!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>
html,
body {
height: 100%;
}
body {
background: black;
overflow: hidden;
}
canvas {
max-width: 100%;
}
</style>
</head>
<body>
<canvas id="c"></canvas>
</body>
<script>
/* 兼容什么的 */
window.requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame;
var canvas = document.getElementById('c'),
ctx = canvas.getContext('2d'),
w = (canvas.width = window.innerWidth),
h = (canvas.height = window.innerHeight),
logoParticles = [],
particleIndex = 0,
logo = new Image(),
hue = 0;
function Particle(x, y) {
this.origX = this.x = x;
this.origY = this.y = y;
this.color = 'white';
this.maxLife = this.random(20);
this.life = 0;
this.vx = this.random(-1, 1);
this.vy = this.random(-1, 1);
this.grav = 0.04;
this.index = particleIndex;
logoParticles[particleIndex] = this;
particleIndex++;
}
Particle.prototype = {
constructor: Particle,
draw: function () {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, 2, 2);
this.update();
},
update: function () {
if (this.life >= this.maxLife) {
logoParticles[this.index].reset();
}
this.x += this.vx;
this.y += this.vy;
this.vy += this.grav;
this.life++;
},
reset: function () {
this.x = this.origX;
this.y = this.origY;
this.life = 0;
this.color = 'hsl(' + hue + ', 100%, 50%)';
this.vx = this.random(-1, 1);
this.vy = this.random(-1, 1);
},
random: function () {
var min = arguments.length == 1 ? 0 : arguments[0],
max = arguments.length == 1 ? arguments[0] : arguments[1];
return Math.random() * (max - min) + min;
},
};
logo.src =
'data:image/png;base64,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';
logo.onload = function () {
var posX = (w - this.width) / 2,
posY = (h - this.height) / 2;
ctx.drawImage(this, posX, posY);
var imgData = ctx.getImageData(0, 0, w, h),
pixels = imgData.data;
for (var y = 0; y < imgData.height; y += 3) {
for (var x = 0; x < imgData.width; x += 3) {
var alpha = pixels[(imgData.width * y + x) * 4 + 3];
if (alpha > 0) {
logoParticles.push(new Particle(x, y));
}
}
}
setTimeout(function () {
animate();
}, 800);
};
function animate() {
ctx.fillStyle = 'rgba(0,0,0,.1)';
ctx.fillRect(0, 0, w, h);
for (var i in logoParticles) {
logoParticles[i].draw();
}
hue += 1;
window.requestAnimationFrame(animate);
}
</script>
</html>
以上代码复制到一个html文件中即可预览。
接下来从程序运行顺序开始解读:
1. 声明全局变量
就是这一段代码:
其中canvas
、ctx
、w
、h
、lolo
四个变量没什么说的,说下其他三个:
logoParticles
:这是canvas元素所有的像素实例的对象,这个数组包含这个canvas所有的像素点,通过遍历每个像素点对其进行粒子偏移的操作。particleIndex
:这是当前遍历的像素点的索引hue
:这个是粒子跳动时的颜色,这里使用的是hsl
颜色表示法。这个表示色相。
不懂什么意思的就先放这,继续往下看。
先不看下面的构造函数Particle()
。声明完全局变量后就是加载图片并绘制到canvas上了:
2. 图片加载完成
就是下面一段代码:
在图片在页面上加载完成以后,就要把图片绘制到canvas上了:
-
上面的
posX
,posY
是将屏幕宽度和高度减去图片宽度后除以二得到的canvas的左上角的x,y轴坐标位置,其目的是让canvas位于整个窗口的正中间。然后就是使用drawImage方法将图片绘制到canvas画布上。 -
imgData
是这个canvas画布的所有像素点数据,然后通过下面的两个for循环遍历所有的像素点数据,遍历每个像素点的alpha值(即透明度)。如果不是透明的(非背景:alpha>0)。就继续接下来的操作。(关于两个for循环是如何遍历出所有像素点数据的可以见我另一篇博文) -
logoParticles.push(new Particle(x, y));
这一步操作是把所有处于canvas像素点x,y位置的像素点作为一个实例对象存到所有需要操作的数组中;便于后续操作。
在所有的像素点数据保存完毕以后。接下来开始执行动画操作(animate()
)。
3. 每个关键帧里重绘画布
4. 重绘完画布以后再重绘canvas每个需要重绘的像素点
重绘每个像素点的操作都封装在Particle()
构造函数中了。就是这两段:
-
this.draw() 每个像素点先绘制一个以该像素点为基本地址的有偏移量的宽高为2的方块
-
this.update() 再把偏移量增减一个随机值(就是vx、vy值)。当随机值(this.life)达到最大值(this.maxLife)时。回到这个像素点的初始位置继续画,并重新获取偏移量(this.reset())。如此往复
其中maxLife
和vx
,vy
都是通过random()
方法获取到的。
5. Particle()构造函数解析
这里的构造函数及其原型是es6之前的写法,为什么写一个constructor:Particle
,本来其实Particle.prototype.constructor
就是指向Particle
的,这么写应该是为了省事,不然的话,这个原型上4个方法就要这么写了:
Particle.prototype.draw = function(){ ... }
Particle.prototype.update = function(){ ... }
Particle.prototype.reset = function(){ ... }
Particle.prototype.random = function(){ ... }
其实这里用ES6的class写法可以改写成下面这样,功能一致:
点击查看代码
class Particle {
constructor(x, y) {
this.origX = this.x = x; // this.origX:像素点原始位置;this.x:像素点当前位置
this.origY = this.y = y;
this.color = 'white'; // 当前像素点颜色
this.maxLife = this.random(20); // 当前像素点跳动的次数
this.life = 0; // 当前像素点已经偏移的距离
this.vx = this.random(-1, 1); // 每次x轴偏移的距离,可以是负的
this.vy = this.random(-1, 1);
this.grav = 0.04;
this.index = particleIndex; // 当前像素点在所有像素点数组(logoParticles)的索引
logoParticles[particleIndex] = this; // 把当前像素点的实例保存到数组中
particleIndex++; // 继续遍历下一个像素点
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, 2, 2);
this.update();
}
update() {
if (this.life >= this.maxLife) {
logoParticles[this.index].reset();
};
this.x += this.vx;
this.y += this.vy;
// this.vy += this.grav;
this.life++;
}
reset() {
this.x = this.origX;
this.y = this.origY;
this.life = 0;
this.color = 'hsl(' + hue + ', 100%, 50%)';
this.vx = this.random(-1, 1);
this.vy = this.random(-1, 1);
}
/**
* 取最大值最小值中间的一个数(或0-最大值的那个值)
*/
random() {
var min = arguments.length == 1 ? 0 : arguments[0],
max = arguments.length == 1 ? arguments[0] : arguments[1];
return Math.random() * (max - min) + min;
}
}
6.小结
其实整个代码的关于canvas的知识并不多,就用到了drawImage,getImageData,fillRect
三个canvas的api,用法很简单。
主要还是每个像素点的实例对象的理解上。logoParticles
这个数组保存的是每个像素点的实例对象,每个实例对象里面包含了当前像素点的初始位置,最大偏移量,已经偏移量,当前x,y轴位置。通过遍历这个实例对象的数组,并调用其draw()方法,即可实现单个像素点的循环重绘。