前言
前面小包实现一个使用粒子构成 “战场小包” 文字炫酷效果,给小包打开粒子世界的大门,粒子世界真的是炫酷,简单的组合变换可以产生超多炫彩效果,今天小包带大家实现粒子的吸力与斥力效果,可以试玩一下,超级解压和快乐。
体验地址: 粒子万有引力与斥力
实现粒子
首先来实现全屏随机粒子分布,本文章共生成 2500
个粒子,位置 (x,y)
通过随机数生成,粒子形状通过 fillRect
绘制。
// 创建粒子类 class Particle { constructor() { // rand 是封装的生成 (n,m) 之间随机数的函数 // x, y 粒子位置 this.x = rand(0, canvas.width); this.y = rand(0, canvas.height); // vx, vy 粒子在x, y 轴的移动速度 this.vx = rand(-1, 1); this.vy = rand(-1, 1); // 粒子大小 this.r = rand(1, 3); // 粒子颜色 this.col = `rgba(${Math.round(rand(150, 200))}, ${Math.round( rand(100, 255) )}, ${Math.round(rand(180, 255))},${Math.random() + 0.2})`; } } // 生成粒子 function initParticle() { for (var i = 0; i < part_num; i++) { P.push(new Particle()); } } 复制代码
满屏幕的粒子生成之后,接下来就来做粒子的效果吧。
万有引力效果
要实现万有引力效果,首先需要一个引力中心,初始位置为中心位置,当鼠标开始移动时,鼠标位置为引力中心。为了看起来更明显,通过 canvas
在引力中心处绘制一个十字架。
// X, Y 为引力中心位置 // X, Y 初始化 X = window.innerWidth / 2; Y = window.innerHeight / 2; // 绘制十字架 ctx.beginPath(); ctx.moveTo(X, Y - 10); ctx.lineTo(X, Y + 10); ctx.moveTo(X - 10, Y); ctx.lineTo(X + 10, Y); ctx.stroke(); // 移动鼠标时,修改引力中心位置 window.onmousemove = function (e) { X = e.clientX; Y = e.clientY; }; 复制代码
引力中心确定后,规定粒子移动方式为沿半径向引力中心靠近,越近速度越快。初始化粒子时,提供 vx,vy
属性为粒子分别沿 x,y
轴移动的速度,因此可以通过修改 vx,vy
属性使粒子不断接近引力中心。
在实现上述效果之前,先来复习一下三角函数的知识:
从上面图中可以得出,r*cos
, r*sin
为 r
分别在 x,y
轴上的投影,如果将 r
换成非常微小的长度,r*cos
, r*sin
即为单位时间内粒子在 x,y
轴上即将移动的距离,也就是速度 vx,vy
。因此我们需要求解出粒子到引力中心的角度 angle
。
var dx = p.x - X, // 水平距离 dy = p.y - Y, // 垂直距离 dist = Math.sqrt(dx * dx + dy * dy), // 到引力中心的距离 angle = Math.atan2(dy, dx); // 角度 复制代码
前端中的坐标系与数学中坐标系略有差异,x
轴正方向相同,但 y
轴正方向向下,各个象限的余弦与正弦大小如下:
复习完上面知识后,接下来就可以计算粒子移动方式了。(粒子越靠近引力中心速度越快)
在坐标系中取三个粒子,我们来实际演练一下:
粒子 | cos /sin |
dx /dy |
len*cos /len*sin |
A | 正/负 | 正/负 | 正/负 |
C | 负/负 | 负/负 | 负/负 |
D | 正/正 | 正/正 | 正/正 |
dx
与 dy
分别为粒子距离引力中心的长度在 x,y
轴上的投影,存在正负值,例如点 A ,dxA > 0, dyA < 0
,因此如果粒子 A 想靠近引力中心 B ,需设置 vx < 0, vy > 0
,来减少 A 与 B 之间的距离。
从表格中我们可以得知, len*cos
和 len*sin
正好与 dx
与 dy
同号,因此设置 -len*cos
和 -len*sin
为粒子粒子的移动速度,就能使粒子 A 不断靠近引力中心 B(len
为任意长度)
// 粒子越靠近引力中心,dist越小,一方面可以保证粒子速度越来越快,另一方面也达成len不断修改的要求 p.vx -= (20 / (p.r * dist)) * Math.cos(angle); p.vy -= (20 / (p.r * dist)) * Math.sin(angle); p.x += p.vx; p.y += p.vy; 复制代码
斥力效果
如果光有引力效果,耍一会大部分粒子都被吸引到中间,显得四周空空旷旷的,玩久了也有几分单调。那咱们就给添加个排斥效果,有吸力,有斥力,效果齐活了。
斥力效果的效果为点击鼠标,粒子沿圆形往四周扩散,为保证扩散效率,设置大扩散半径。
有了引力的实现基础,粒子向四周只需保证 len*cos
和 len*sin
正好与 dx
与 dy
同号即可。
p.vx += (250 / (p.r * dist)) * Math.cos(angle); p.vy += (250 / (p.r * dist)) * Math.sin(angle); 复制代码
粒子的斥力效果是不是很解压,但也能发现一个比较明显的问题,很多粒子被排斥到页面之外,页面中的粒子越来越少了,这可不好玩啊,因此添加对边界处理逻辑。
边界处理
边界处理逻辑不难实现,当粒子到达边界时,例如碰到垂直方向的上边界,那么意味着粒子的 vy
是负值,修改其为异号,那么粒子就可以反弹回来,其余方向处理类似。
function bounce(b) { // 左边界 if (b.x < b.r) { b.x = b.r; b.vx *= -1; } // 右边界 if (b.x > canvas.width - b.r) { b.x = canvas.width - b.r; b.vx *= -1; } // 上边界 if (b.y - b.r < 0) { b.y = b.r; b.vy *= -1; } // 下边界 if (b.y > canvas.height - b.r) { b.y = canvas.height - b.r; b.vy *= -1; } } 复制代码
源码仓库
源码地址: 粒子的万有引力与斥力
体验地址: 粒子的万有引力与斥力
如果感觉有帮助的话,别忘了给小包点个 ⭐ 。