超解压,实现粒子的万有引力与斥力效果

5,400 阅读3分钟

前言

前面小包实现一个使用粒子构成 “战场小包” 文字炫酷效果,给小包打开粒子世界的大门,粒子世界真的是炫酷,简单的组合变换可以产生超多炫彩效果,今天小包带大家实现粒子的吸力与斥力效果,可以试玩一下,超级解压和快乐。

体验地址: 粒子万有引力与斥力

实现粒子

首先来实现全屏随机粒子分布,本文章共生成 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());
    }
}

particles.png

满屏幕的粒子生成之后,接下来就来做粒子的效果吧。

万有引力效果

要实现万有引力效果,首先需要一个引力中心,初始位置为中心位置,当鼠标开始移动时,鼠标位置为引力中心。为了看起来更明显,通过 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*sinr 分别在 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 轴正方向向下,各个象限的余弦与正弦大小如下:

condiFront.jpg

复习完上面知识后,接下来就可以计算粒子移动方式了。(粒子越靠近引力中心速度越快)

在坐标系中取三个粒子,我们来实际演练一下:

zuobiao.png

粒子cos/sindx/dylen*cos/len*sin
A正/负正/负正/负
C负/负负/负负/负
D正/正正/正正/正

dxdy 分别为粒子距离引力中心的长度在 x,y 轴上的投影,存在正负值,例如点 A ,dxA > 0, dyA < 0 ,因此如果粒子 A 想靠近引力中心 B ,需设置 vx < 0, vy > 0 ,来减少 A 与 B 之间的距离。

从表格中我们可以得知, len*coslen*sin 正好与 dxdy 同号,因此设置 -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;

attact.gif

斥力效果

如果光有引力效果,耍一会大部分粒子都被吸引到中间,显得四周空空旷旷的,玩久了也有几分单调。那咱们就给添加个排斥效果,有吸力,有斥力,效果齐活了。

斥力效果的效果为点击鼠标,粒子沿圆形往四周扩散,为保证扩散效率,设置大扩散半径

有了引力的实现基础,粒子向四周只需保证 len*coslen*sin 正好与 dxdy 同号即可。

p.vx += (250 / (p.r * dist)) * Math.cos(angle);
p.vy += (250 / (p.r * dist)) * Math.sin(angle);

bounce.gif

粒子的斥力效果是不是很解压,但也能发现一个比较明显的问题,很多粒子被排斥到页面之外,页面中的粒子越来越少了,这可不好玩啊,因此添加对边界处理逻辑。

边界处理

边界处理逻辑不难实现,当粒子到达边界时,例如碰到垂直方向的上边界,那么意味着粒子的 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;
  }
}

attactAndBounce.gif

源码仓库

源码地址: 粒子的万有引力与斥力

体验地址: 粒子的万有引力与斥力

如果感觉有帮助的话,别忘了给小包点个 ⭐ 。

往期精彩文章

后语

伙伴们,如果大家感觉本文对你有一些帮助,给阿包点一个赞👍或者关注➕都是对我最大的支持。

另外如果本文章有问题,或者对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!