canvas+particle 实现文字粒子效果。

这里简单加个备注,之前群里有人问这个是如何实现的,当时以为是three.js效果,看了原网站是利用dat.gui.js脚本,大概是把base64的图片进行粒子化输出。不过我仍然没有想明白他是如何把canvas背景进行透明化处理的。
很遗憾canvas特效这方面没有具体的教程或者博客文章作为参考,只是国外的友人进行了相关的操作,我移植过来进行了改写和必要的注释,最重要的目的不是去学习前端的canvas而是这个特效如果透明化处理会是很酷炫的博客签名。
以下是代码,作为记录,虽然已经可以完美嵌入到前端页面之中,不过无法透明化背景处理,失去了使用价值。但还是希望有一天可以改写清楚,不浪费我半天的时光。
效果如下:
SDGIF_Rusult_1.gif
代码如下,复制到html,改后缀为html即可运行。

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>

		<style>
			#myCanvas {
				width: 100%;
			}

			#contain {
				margin: 0 auto;
				width: 31.25rem;
				height: 20rem;
  

			}
		</style>
	</head>
	<body>
		<!-- 简单来说,这个原理为:在黑色canvas上用白色的笔画出文字,然后把整个canvas输出为图片对象,然后加载每一个像素进行判断;
		如果是白色的rgb格式则随机赋予颜色从而重新输出整张画。鼠标的位置变动都会刷新图片。 -->
		<div id="contain">
			<canvas id="myCanvas"></canvas>
		</div>
		<script>
			/*jshint esversion:6*/
			var contain = document.getElementById('contain');
			var can = document.getElementById('myCanvas');
			var ctx = can.getContext('2d');

			function resize() {
				//can.width = window.innerWidth;
				can.width = contain.offsetWidth;;
				//can.height = window.innerHeight;
				can.height = contain.offsetHeight;;
			}
			const max_radius = 3; //最大粒子半径
			const min_radius = 1; //最小粒子半径
			const drag = 30; //粒子恢复速度
			window.onresize = function() {
				resize();
			};

			function cfill() {
				ctx.fillStyle = "#000"; //填充样式,可填充渐变色
				//ctx.fillStyle ="rgba(255, 255, 255, 0)";
				ctx.fillRect(0, 0, can.width, can.height); //填充为矩形,x,y为矩形左上角位置坐标
				ctx.globalAlpha=0.5;
				ctx.fill();
				// ctx.fillText()
			}
			var mouse = { //声明mouse对象,该对象有两个属性x,y.可以通过mouse.x调用。
				x: -1000,
				y: -1000
			};
			can.onmousemove = function(e) { //canvas.addEventListener('mousemove', doMouseMove,false);
				//mouse.x = e.clientX;//鼠标相对于浏览器窗口可视区域的X,Y坐标(窗口坐标)。 Canvas上鼠标事件中不能直接获取鼠标在Canvas的坐标,所获取的都是基于整个屏幕的坐标。
				mouse.x = e.offsetX;
				//mouse.y = e.clientY;
				mouse.y = e.offsetY;
			};
			can.ontouchmove = function(e) { //触摸事件,手指的相对位置。
				mouse.x = e.touches[0].offsetX;
				//mouse.x = e.touches[0].clientX;
				mouse.y = e.touches[0].offsetY;
				//mouse.y = e.touches[0].clientY;
			};
			resize();
			cfill();

			function distance(x, y, x1, y1) { //计算两点之间的距离,这里明显是计算粒子与鼠标箭头的距离。
				return Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y));
			}

			class Particle { //这是粒子类 js的面向对象属于js高级
				constructor(pos, target, vel, color, radius) { //构造器
					this.pos = pos;
					this.target = target;
					this.vel = vel;
					this.color = color;
					this.radius = radius;
					var arr = [-1, 1];
					this.direction = arr[~~(Math.random() * 2)] * Math.random() / 10; //随机化粒子初始方向,unity会接触到。0表示粒子保持默认方向,1表示完全随机方向。~~和>>>0语法差不多为的是保证数值为正整数。
				}
				set(type, value) {
					this[type] = value;
				}
				update() {
					this.radius += this.direction;
					this.vel.x = (this.pos.x - this.target.x) / drag; //粒子位置和目标位置之差除以拉伸速度为实际位置
					this.vel.y = (this.pos.y - this.target.y) / drag;
					if (distance(this.pos.x, this.pos.y, mouse.x, mouse.y) < 100) { //粒子和鼠标箭头的距离
						this.vel.x += this.vel.x - (this.pos.x - mouse.x) / 15;
						this.vel.y += this.vel.y - (this.pos.y - mouse.y) / 15;
					}
					if (this.radius >= max_radius) {
						this.direction *= -1;
					}
					if (this.radius <= 1) {
						this.direction *= -1;
					}
					this.pos.x -= this.vel.x;
					this.pos.y -= this.vel.y;
				}
				draw() {
					ctx.beginPath();
					ctx.fillStyle = this.color; //填充颜色样式
					ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI * 2); //画一个圆x,y为圆心坐标,后面依次是半径,起始角和终止角度。
					ctx.fill();
				}
			}
			var particles = [];
			var colors = ["#ff0000", "#ffff7f", "#00007f", "#ff00ff", "#f9f9f9"];
			var bool = true;
			var current = 0,
				i; //意思是声明两个变量,一个current=0,一个i。
			function changeText(text) {
				var current = 0,
					temp, radius, color;
				//cfill();
				ctx.fillStyle = "#fff";
				ctx.font = "80px Times";
				ctx.fillText(text, can.width * 0.5 - ctx.measureText(text).width * 0.5, can.height * 0.5 + 60); //text,x,y 相对于画布的x,y坐标开始绘制文本。
				var data = ctx.getImageData(0, 0, can.width, can.height).data; //通过 getImageData() 复制画布上指定矩形的像素数据。这是像素的数组对于rgba来说。
				//cfill();
				// 对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
				// R - 红色 (0-255)
				// G - 绿色 (0-255)
				// B - 蓝色 (0-255)
				// A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)
				// color/alpha 以数组形式存在,并存储于 ImageData 对象的 data 属性中。
				for (i = 0; i < data.length; i += 8) {//实际是遍历像素的二维数组,一维是像素数组长度,二维是每个像素的r g b a。+8是跳一个元素。
					temp = {
						x: (i / 4) % can.width,
						y: ~~((i / 4) / can.width)
					}; //相对定位
					if (data[i] !== 0 && ~~(Math.random() * 5) == 1 /*(temp.x % (max_radius+1) === 0 && temp.y % (max_radius+1) === 0)*/ ) {
						if (data[i + 4] !== 255 || data[i - 4] !== 255 || data[i + can.width * 4] !== 255 || data[i - can.width * 4] !==
							255) {//前面画笔为白色#fff 即rgb(255.255.255)。
							if (current < particles.length) {
								particles[current].set("target", temp);
							} else {
								radius = max_radius - Math.random() * min_radius;
								temp = {
									x: Math.random() * can.width,
									y: Math.random() * can.height
								};
								if (bool) {
									temp = {
										x: (i / 4) % can.width,
										y: ~~((i / 4) / can.width)
									};
								}
								color = colors[~~(Math.random() * colors.length)];//筛选出来的元素随机颜色赋值。
								var p = new Particle(
									temp, {
										x: (i / 4) % can.width,
										y: ~~((i / 4) / can.width)
									}, {
										x: 0,
										y: 0
									},
									color,
									radius);
								particles.push(p);
							}
							++current;
						}
					}
				}
				bool = false;
				particles.splice(current, particles.length - current);
			}

			function draw() {
				cfill();
				for (i = 0; i < particles.length; ++i) {
					particles[i].update();
					particles[i].draw();
				}
			}
			changeText("FlitSneak");
			setInterval(draw, 1);//1的单位是毫秒
		</script>

	</body>
</html>

由于本人并不了解canvas,最初的想法是用透明canvas输出彩色字体,然后发现透明canvas无法有效检测翻转色彩,是否要遍历像素对透明部分进行保留也让我绕了很久。第二种想法是遍历每个像素时,如果像素是白色则替换成彩色像素,否则其余的部分赋值为透明色。但要尝试这种想法又有两个困难,一是我对js高级语法不熟悉,对canvas元素的操作也肯定要踩很长时间坑出不来。如果有人能碰巧看到又碰巧实现了,那万分感谢,务必让我对此代码膜拜一二。

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议