引言
本来是想学习一下 Pixi.js 的粒子效果,结果做出来的效果粗粗糙糙的,想要做出好看的效果粒子运动轨迹离不开算法,还需要些时间研究= =。于是在网上看了一篇用 canvas 做的粒子 demo 博客,感觉也挺不错的,实现思路简单,于是自己也做了一个,但效果跟前者是不一样的,是在前者的基础上做了些改变。
目录
-
实现原理
1.1 让粒子动起来
1.2 比对
1.3 画线
代码
另一种效果
整合封装
总结
了解更多
1、实现原理
先看下效果图:
1.1 让粒子动起来
效果还是挺炫酷的,实现起来其实也并不难,就是在 canvas
画布上随机生成点位,用 requestAnimationFrame
方法不断更新每个粒子的 x,y 坐标值,就做到了简单的粒子运动效果。
在粒子的 x 或 y 值大于 canvas 宽高或小于 0 时做反向运动:定义两个变量 x, y 轴的运动速度 sx
sy
设置速度为 Math.random() * 1
, 0~1 区间,正常情况下 粒子的 x,y 值等于 x += sx; y += sy;
在大于 canvas 宽高或小于 0 时将速度值赋值为负数 sx = - sx; sy = -sy;
做反向运动。
1.2 比对
用一个变量数组存储需要绘制的每个粒子信息,在定义一个鼠标粒子对象加入数组当做一个粒子比对,用双重 for 循环让当前粒子跟其他粒子位逐个比对,第一层循环让每个粒子开始移动,判断如果是鼠标粒子则把鼠标移动的 x,y 坐标传给鼠标粒子对象的 x,y 值,第二层循环计算当前粒子与其他粒子距离,如果粒子与粒子的距离小于某值则进行画线。
1.3 画线
在每次 requestAnimationFrame
更新粒子 x,y 位置前先清空上一次粒子的位置,定义一个 max 值用于判断粒子与粒子之间距离是否小于 max 值如果小于 max 值,在他们之间画线,画线可以用 hsla(色相, 饱和度, 亮度, 透明度)
函数,色值用一个变量做累加就好了,透明度也需要一个变量,可以看到效果上在粒子与粒子距离逐渐大于 max 值时线条是一个逐渐弱化的效果:(max - 两点距离) / max;
。
效果图上可以看到粒子会跟着鼠标走,判断如果比对粒子是鼠标点粒子,并且距离小于 max / 2
时让与鼠标粒子比对的其他粒子的 x,y 值递减,比如粒子与粒子间距离小于 100 就给他画线,此时粒子 x,y 值还在做速度递增运动(现在的递增是逐渐靠近鼠标点位),在小于 50 的时候做反方向运动(逐渐远离鼠标点),大于50靠近鼠标点,小于50远离鼠标点,这样就行成了一个看似吸附点效果。
2、代码
let mons = {
x: -100,
y: -100,
r: 1
};
class Point {
constructor (max, name) {
this.x = Math.random() * canvas.width; // x坐标
this.y = Math.random() * canvas.width; // y坐标
this.r = 1;
this.max = max;
this.name = name;
this.sx = Math.random() * 2 - 1; // 速度
this.sy = Math.random() * 2 - 1; // 速度
this.hue = random( 60, 100 );
this.brightness = random(0, 250);
}
// 随机移动
move (x, y) {
this.x += this.sx;
this.y += this.sy;
this.hue += 1;
if (this.name) {
this.x = x;
this.y = y;
}
if (this.x > canvas.width || this.x < 0) this.sx = -this.sx;
if (this.y > canvas.height || this.y < 0) this.sy = -this.sy;
}
// 画线
drawLine (ctx, p, isDraw){
let dx = this.x -p.x;
let dy = this.y -p.y;
let d = dx * dx + dy * dy;
if (d < p.max) {
if (p === mons.point && d > (p.max / 2)) {
this.x -= dx * 0.03;
this.y -= dy * 0.03;
}
let alpha = (p.max - d) / p.max;
ctx.beginPath();
ctx.strokeStyle ='hsla(' + this.hue + ', 100%, ' + this.brightness + '%, ' + alpha + ')'
ctx.strokeWidth = 1;
ctx.moveTo(this.x, this.y);
ctx.lineTo(p.x, p.y);
ctx.stroke();
}
}
}
let points = [];
for (let i = 0; i < 200; i++) {
points.push(new Point(6000));
}
mons.point = new Point(20000, 'touch');
points.push(mons.point);
const paint = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < points.length; i++) {
points[i].move();
if (points[i].name) {
points[i].move(mons.x, mons.y);
}
for (let j = i + 1; j < points.length; j++){
if (points[j] === mons.point) {
points[i].drawLine(ctx, points[j], true);
}
points[i].drawLine(ctx, points[j]);
}
}
}
function random( min, max ) {
return Math.random() * ( max - min ) + min;
}
function loop(params) {
requestAnimationFrame(loop);
paint();
}
loop();
3、另一种效果
效果图:
这个效果也是挺不错的,看起来很丝滑。
这个效果是在上一个效果的基础上做了些修改,改成了类似鼠标拖尾的效果,实现起来比上一个效果也简单一些。
for
循环 new Point
类添加到 points
粒子数组中,requestAnimationFrame
执行粒子运动与画线,去掉了鼠标粒子与其他粒子距离的判断只在鼠标移动时 new Point
类添加到粒子数组把鼠标的 x,y 值传入 Point
类: points.push(new Point(x, y))
在粒子数组长度大于 80 shift()
移除粒子数组第一项。
canvas.addEventListener('mousemove', function(e) {
e.preventDefault();
// 根据鼠标位置添加点进数组
points.push(new Point(e.clientX,e.clientY));
// 多余80个点移除数组第一个点
if (points.length > 80) {
points.shift();
}
});
4、整合封装
做出两个效果后就尝试着整合封装了一下,通过传参 isBg 判断展现的是哪种效果 true 为第一种效果 false 为第二种,pc端上两种效果都能呈现,但移动端上目前设置只能展现第二种,第一种效果在手机上体验感比较差 100 个粒子以上运动就会比较卡了。
使用方法:
引入 js js 源码地址
let point = new particleLine({
canvas: canvas, // canvas
width: 宽, // canvas宽
height: 高, // canvas高
num: 粒子数, // 粒子数
isBg: true // true: 第一种效果 false: 第二种效果
});
point.play(); // 执行
demo 链接:demo
参考博客:
html canvas粒子线条组合动画背景特效
5、总结
这个 demo 实现起来还是比较简单的,效果也挺不错的。难点应该在于鼠标粒子和普通粒子间的计算。canvas 粒子还有很多其他炫酷的效果,比如粒子变换特定形状,用getImageData
方法获取画布指定矩形像素,通过获取到的点位跟 RGBA 值用粒子绘画出来。
6、了解更多
原文链接:canvas 粒子线条