浮动粒子制作404动画页面
前言:之前在网上看到了这个效果,之后我在做项目的时候,正好将他放进了项目里面,这篇博客就介绍一下该效果的原理;
1.首先是基础的设置
const canvas = <HTMLCanvasElement>document.getElementById('canvas');
let ctx: any = canvas.getContext('2d');
const W: number = canvas.width = window.innerWidth;
const H: number = canvas.height = window.innerHeight;
const mFound: number = H * 2.5; //found 的粒子数量
const mStr: number = H * 1.5;// 404的例子数量
ctx.textAlign = "center";//居中
canvas 的大小是占据整个屏幕;
2.绘出字
const str: string = '404';
ctx.textBaseline = 'bottom';
ctx.font = `${H / 2}px 'Arial', sans-serif`;
ctx.fillText(str, W / 2, (H + H / 4) / 2, W);
const found: string = 'Not Found';
ctx.textBaseline = 'top';
ctx.font = `${H / 6}px 'Arial', sans-serif`;
ctx.fillText(found, W / 2, (H + H / 4) / 2, W);
通过 fillText 的最后一个值,可以让字的大小不超过该值,我选择的是,浏览器的宽度,经我手机上检验,字的大小正好到达手机的宽度;
再说一下我这边要写2次,是因为字的尺寸是不一样的;
3.获取粒子
let imageDataStr = ctx.getImageData(0, 0, W, (H + H / 4) / 2);
let imageDataFound = ctx.getImageData(0, (H + H / 4) / 2, W, (H - H / 4) / 2);
interface dotsType {
x: number
y: number
r: number
a: number
lx: number
rx: number
v: number
}
function getDots(imageData, isStr: boolean): dotsType[] {
let dots = [];
let index: number = 0;
for (let i = 0; i < W; i += 2) {
for (let j = 0; j < H; j += 2) {
let k = 4 * (i + j * W);
if (imageData.data[k + 3] > 0) {
dots[index++] = {
x: i,
y: isStr ? j : j + (H + H / 4) / 2,
r: Math.random() * (isStr ? 8 : 3),
a: Math.random(),
lx: isStr ? i - 4 : i - 2,
rx: isStr ? i + 4 : i + 2,
v: (Math.random() - .5) * (isStr ? .8 : .4)
}
}
}
}
let newDots = [];
const m = isStr ? mStr : mFound;
if (m && (dots.length > m)) {
for (let i = 0; i < m; i++) {
newDots.push(dots[Math.floor(Math.random() * dots.length)]);
}
} else {
newDots = dots;
}
return newDots;
}
使用 canvas 的 getImageData函数,将 canvas 读取为 imageData 格式,再根据图片的像素的值来获取写有字的例子的位置,将其添加入一个粒子数组,顺便加入粒子的半径,透明度,运动半径,和运动速度,而他接受的isStr参数,是为了区分是 str 还是 found 而区别的;
为什么我要这样用一个参数来区分呢? 因为原来我看到的效果他是拿2个 canvas 叠在一起的,十分麻烦,而我根据定位和对应高度,加上 getImageData 的位置参数可以解决这个问题;
这新建了 newDots 数组是为了根据开头所设置的粒子数来打乱原数组的顺序;
4.渲染
let dataStr: dotsType[] = getDots(imageDataStr, true);
let DataFound: dotsType[] = getDots(imageDataFound, false);
const data: dotsType[] = [...dataStr, ...DataFound];
function render(): void {
ctx.fillStyle = "#4db9ea";
ctx.fillRect(0, 0, W, H);
for (let i = 0; i < data.length; i++) {
let temp = data[i];
ctx.beginPath();
ctx.fillStyle = `rgba(255,255,255,${temp.a})`;
temp.x = temp.x + temp.v;
temp.y = temp.y + temp.v;
if (temp.x < temp.lx || temp.x > temp.rx) {
temp.v = -temp.v;
}
ctx.arc(temp.x, temp.y, temp.r, 0, 2 * Math.PI);
ctx.fill()
}
}
render();
渲染出对于半径和透明度的圆;
因为分成了2个 imageData, 所以需要后面将2个粒子数组合并;并且根据每个数组的速度,来进行一个来回的直线运动;
5.计时
const requestAnimFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame;
let startTime = 0;
function animation(time: number = 0): void {
if (time - startTime > 25) {
render();
startTime = time;
}
requestAnimFrame(animation)
};
animation();
本来的话直接使用requestAnimFrame
就行了,但是我调低了帧率,影响不大;
最后:
demo:点击查看
github:https://github.com/Grewer/JsDemo/tree/master/floatParticles