其实很早之前就发过一个,但是呢很粗糙,今天有空索性重新写了一个,先看效果
页面结构就一个canvas 和 一点基本样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>粒子文字</title>
<style>
* {
padding: 0;
margin: 0;
}
body {
position: relative;
width: 100vw;
height: 100vh;
background-color: #000000;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="./index.js"></script>
</html>
重点是 JS 怎么写
这里把全部的JS代码分成了四块 合起来按顺序放在一个js文件就行了
第一步
//当前窗口高宽
function getWid_Hei() {
let width, height;
if (window.innerWidth) {
width = window.innerWidth;
height = window.innerHeight;
} else if (document.compatMode === "BackCompat") {
width = document.body.clientWidth;
height = document.body.clientHeight;
} else {
width = document.documentElement.clientWidth;
height = document.documentElement.clientHeight;
}
return {
clientWidth: Math.floor(width),
clientHeight: Math.floor(height)
}
}
//解构得到宽高 并保存
let { clientWidth, clientHeight } = getWid_Hei();
//画布
let canvas = document.getElementById('canvas');
//初始化
canvas.width = clientWidth;
canvas.height = clientHeight;
//拿到画笔
const ctx = canvas.getContext('2d');
第二步
//先用canvas把文字画出来
ctx.font = "900 100px Arial";
//用户自己输入 文字
let text = prompt('输入几个字 字越多所需时间越长', '');
if (text == null) {
text = '空';
}
ctx.textAlign = "center";
//拿到画布上文字区域宽度 ctx.measureText(text).width
let text_width = ctx.measureText(text).width;
//把文字画出来
ctx.fillText(text, clientWidth / 2, clientHeight / 2);
//获取文字图片的一个图片信息 ,就是像素信息 我们要用到imgData.data 这里面是像素信息了
let imgData = ctx.getImageData((clientWidth / 2 - text_width / 2), clientHeight / 2 - 100, text_width, 120);
//清空 画布 ,不要文字显示出来
ctx.clearRect(0, 0, clientWidth, clientHeight);
第三步
//所有的粒子
let allParticle = [];
//运动中的粒子
let particle = [];
//所有到达目的地的粒子
let targetParticle = [];
//已经生成了多少粒子
let nowParticle = 0;
//装饰背景的粒子
let backParticle = [];
//最大装饰粒子数量
let maxBgParticle = 100;
//已有装饰粒子数量
let bgParticle = 0;
/**
* 获取有用的粒子位置 因为有部分是透明的 不需要
* 然后这些粒子都是即将运动的粒子 push 到 allParticle 中
*/
(function filterate() {
//数组中每四位数构成一个像素点 是 rgba 的形式 找到透明度为 0 的 去掉
for (let i = 0, max = imgData.data.length; i < max; i += 4) {
//这个像素点的透明度
const alpha = imgData.data[i + 3];
//获取像素点在数组中的位置
const x = Math.floor(i / 4) % imgData.width;
const y = Math.floor(i / 4 / imgData.width);
//透明度不是 0 外加 额外舍去一部分
if (alpha && x % 3 === 0 && y % 3 === 0) {
//数组中的位置
allParticle.push({
x: x + clientWidth / 2 - text_width / 2,
y: y + clientHeight / 2
});
}
}
})();
/**
* 在画布上随机生成粒子
*/
function produceParticle() {
//粒子全部生成后不在生成
if (nowParticle == allParticle.length) {
return;
}
//粒子的初始位置
let sx = Math.floor(Math.random() * clientWidth);
let sy = Math.floor(Math.random() * clientHeight);
//粒子颜色随机
let color = `hsl(${Math.floor(Math.random() * 360)}, 50%, 50%)`;
//粒子移动速度
let speed = Math.floor(Math.random() * 10 + 10) * 10;
//粒子 x y 每次偏移量
let speedX = (allParticle[nowParticle].x - sx) / speed;
let speedY = (allParticle[nowParticle].y - sy) / speed;
/*
* 放到移动粒子数组中
* sx sy 起点位置 zx zy 终点位置 color 粒子颜色 speedX speedY 粒子每次移动偏移量
*/
particle.push({
sx: sx,
sy: sy,
color: color,
zx: allParticle[nowParticle].x,
zy: allParticle[nowParticle].y,
speedX: speedX,
speedY: speedY,
arrived: false
});
nowParticle += 1;
}
/**
* 粒子向各自的目标移动移动
*/
function moveParticle() {
//粒子全部到达目的地
if (particle.length == targetParticle.length) {
return;
}
//粒子移动
for (let i = 0; i < particle.length; i++) {
//每次移动一个偏移量
particle[i].sx += particle[i].speedX;
particle[i].sy += particle[i].speedY;
//到达终点 取绝对值 1.5 的误差认为到了
if (Math.abs(particle[i].sx - particle[i].zx) <= 1.5 && Math.abs(particle[i].sy - particle[i].zy) <= 1.5 && !particle[i].arrived) {
//保存到达目的地的粒子信息 只需要最终位置
targetParticle.push({
x: particle[i].zx,
y: particle[i].zy
})
particle[i].arrived = true;
particle[i].color = 'rgba(0,0,0,0)';
//每当一个粒子到达目的地 就生成一个装饰粒子
noEndParticle();
}
}
//装饰粒子移动
for (let i = 0; i < backParticle.length; i++) {
//每次移动一个偏移量
backParticle[i].sx += backParticle[i].speedX;
backParticle[i].sy += backParticle[i].speedY;
//到达终点 取绝对值 1.5 的误差认为到了
if (Math.abs(backParticle[i].sx - backParticle[i].zx) <= 1.5 && Math.abs(backParticle[i].sy - backParticle[i].zy) <= 1.5) {
backParticle.splice(i, 1);
bgParticle -= 1;
i -= 1;
noEndParticle();
}
}
}
/**
* 绘画粒子
* 画出在移动的粒子
* 画出到达目的地的粒子
*/
function drawParticle() {
//清空画布
ctx.clearRect(0, 0, clientWidth, clientHeight);
//如果粒子全部到达目的地
if (particle.length == targetParticle.length) {
particle = [];
}
//在移动的粒子
for (let i = 0; i < particle.length; i++) {
ctx.beginPath();
ctx.fillStyle = particle[i].color;
ctx.arc(particle[i].sx, particle[i].sy, 5, 0, 2 * Math.PI, false);
ctx.fill();
ctx.closePath();
}
//到达目的地的粒子
for (let i = 0; i < targetParticle.length; i++) {
ctx.beginPath();
ctx.fillStyle = 'orange';
ctx.fillRect(targetParticle[i].x, targetParticle[i].y, 2, 2);
ctx.closePath();
}
//装饰粒子
for (let i = 0; i < backParticle.length; i++) {
ctx.beginPath();
ctx.fillStyle = backParticle[i].color;
ctx.arc(backParticle[i].sx, backParticle[i].sy, 5, 0, 2 * Math.PI, false);
ctx.fill();
ctx.closePath();
}
}
/**
* 移动粒子到达目的地后 生成一个漫无目的的粒子 装饰背景
*/
function noEndParticle() {
//到达最大装饰粒子数量不再生成
if (bgParticle == maxBgParticle) {
return;
}
//粒子的初始位置
let sx = Math.floor(Math.random() * clientWidth);
let sy = Math.floor(Math.random() * clientHeight);
//粒子的终点位置
let zx = Math.floor(Math.random() * clientWidth);
let zy = Math.floor(Math.random() * clientHeight);
//粒子颜色随机
let color = `hsl(${Math.floor(Math.random() * 360)}, 50%, 50%)`;
//粒子移动速度
let speed = Math.floor(Math.random() * 10 + 10) * 10;
//粒子 x y 每次偏移量
let speedX = (zx - sx) / speed;
let speedY = (zy - sy) / speed;
/*
* 放到移动粒子数组中
* sx sy 起点位置 zx zy 终点位置 color 粒子颜色 speedX speedY 粒子每次移动偏移量
*/
backParticle.push({
sx: sx,
sy: sy,
color: color,
zx: zx,
zy: zy,
speedX: speedX,
speedY: speedY,
});
bgParticle += 1;
}
第四步
这里用的是 请求关键帧 用定时器也可以
//开始
(function init() {
produceParticle();
moveParticle();
drawParticle();
requestAnimationFrame(init);
})();
OK 感谢观看