引言
本來是想學(xué)習(xí)一下 Pixi.js 的粒子效果,結(jié)果做出來的效果粗粗糙糙的,想要做出好看的效果粒子運(yùn)動(dòng)軌跡離不開算法,還需要些時(shí)間研究= =。于是在網(wǎng)上看了一篇用 canvas 做的粒子 demo 博客,感覺也挺不錯(cuò)的,實(shí)現(xiàn)思路簡單,于是自己也做了一個(gè),但效果跟前者是不一樣的,是在前者的基礎(chǔ)上做了些改變。
目錄
-
實(shí)現(xiàn)原理
1.1 讓粒子動(dòng)起來
1.2 比對(duì)
1.3 畫線
代碼
另一種效果
整合封裝
總結(jié)
了解更多
1、實(shí)現(xiàn)原理
先看下效果圖:
1.1 讓粒子動(dòng)起來
效果還是挺炫酷的,實(shí)現(xiàn)起來其實(shí)也并不難,就是在 canvas 畫布上隨機(jī)生成點(diǎn)位,用 requestAnimationFrame 方法不斷更新每個(gè)粒子的 x,y 坐標(biāo)值,就做到了簡單的粒子運(yùn)動(dòng)效果。
在粒子的 x 或 y 值大于 canvas 寬高或小于 0 時(shí)做反向運(yùn)動(dòng):定義兩個(gè)變量 x, y 軸的運(yùn)動(dòng)速度 sx sy 設(shè)置速度為 Math.random() * 1, 0~1 區(qū)間,正常情況下 粒子的 x,y 值等于 x += sx; y += sy; 在大于 canvas 寬高或小于 0 時(shí)將速度值賦值為負(fù)數(shù) sx = - sx; sy = -sy; 做反向運(yùn)動(dòng)。
1.2 比對(duì)
用一個(gè)變量數(shù)組存儲(chǔ)需要繪制的每個(gè)粒子信息,在定義一個(gè)鼠標(biāo)粒子對(duì)象加入數(shù)組當(dāng)做一個(gè)粒子比對(duì),用雙重 for 循環(huán)讓當(dāng)前粒子跟其他粒子位逐個(gè)比對(duì),第一層循環(huán)讓每個(gè)粒子開始移動(dòng),判斷如果是鼠標(biāo)粒子則把鼠標(biāo)移動(dòng)的 x,y 坐標(biāo)傳給鼠標(biāo)粒子對(duì)象的 x,y 值,第二層循環(huán)計(jì)算當(dāng)前粒子與其他粒子距離,如果粒子與粒子的距離小于某值則進(jìn)行畫線。
1.3 畫線
在每次 requestAnimationFrame 更新粒子 x,y 位置前先清空上一次粒子的位置,定義一個(gè) max 值用于判斷粒子與粒子之間距離是否小于 max 值如果小于 max 值,在他們之間畫線,畫線可以用 hsla(色相, 飽和度, 亮度, 透明度) 函數(shù),色值用一個(gè)變量做累加就好了,透明度也需要一個(gè)變量,可以看到效果上在粒子與粒子距離逐漸大于 max 值時(shí)線條是一個(gè)逐漸弱化的效果:(max - 兩點(diǎn)距離) / max;。
效果圖上可以看到粒子會(huì)跟著鼠標(biāo)走,判斷如果比對(duì)粒子是鼠標(biāo)點(diǎn)粒子,并且距離小于 max / 2 時(shí)讓與鼠標(biāo)粒子比對(duì)的其他粒子的 x,y 值遞減,比如粒子與粒子間距離小于 100 就給他畫線,此時(shí)粒子 x,y 值還在做速度遞增運(yùn)動(dòng)(現(xiàn)在的遞增是逐漸靠近鼠標(biāo)點(diǎn)位),在小于 50 的時(shí)候做反方向運(yùn)動(dòng)(逐漸遠(yuǎn)離鼠標(biāo)點(diǎn)),大于50靠近鼠標(biāo)點(diǎn),小于50遠(yuǎn)離鼠標(biāo)點(diǎn),這樣就行成了一個(gè)看似吸附點(diǎn)效果。
2、代碼
let mons = {
x: -100,
y: -100,
r: 1
};
class Point {
constructor (max, name) {
this.x = Math.random() * canvas.width; // x坐標(biāo)
this.y = Math.random() * canvas.width; // y坐標(biāo)
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);
}
// 隨機(jī)移動(dòng)
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、另一種效果
效果圖:
這個(gè)效果也是挺不錯(cuò)的,看起來很絲滑。
這個(gè)效果是在上一個(gè)效果的基礎(chǔ)上做了些修改,改成了類似鼠標(biāo)拖尾的效果,實(shí)現(xiàn)起來比上一個(gè)效果也簡單一些。
for 循環(huán) new Point 類添加到 points 粒子數(shù)組中,requestAnimationFrame 執(zhí)行粒子運(yùn)動(dòng)與畫線,去掉了鼠標(biāo)粒子與其他粒子距離的判斷只在鼠標(biāo)移動(dòng)時(shí) new Point 類添加到粒子數(shù)組把鼠標(biāo)的 x,y 值傳入 Point 類: points.push(new Point(x, y)) 在粒子數(shù)組長度大于 80 shift() 移除粒子數(shù)組第一項(xiàng)。
canvas.addEventListener('mousemove', function(e) {
e.preventDefault();
// 根據(jù)鼠標(biāo)位置添加點(diǎn)進(jìn)數(shù)組
points.push(new Point(e.clientX,e.clientY));
// 多余80個(gè)點(diǎn)移除數(shù)組第一個(gè)點(diǎn)
if (points.length > 80) {
points.shift();
}
});
4、整合封裝
做出兩個(gè)效果后就嘗試著整合封裝了一下,通過傳參 isBg 判斷展現(xiàn)的是哪種效果 true 為第一種效果 false 為第二種,pc端上兩種效果都能呈現(xiàn),但移動(dòng)端上目前設(shè)置只能展現(xiàn)第二種,第一種效果在手機(jī)上體驗(yàn)感比較差 100 個(gè)粒子以上運(yùn)動(dòng)就會(huì)比較卡了。
使用方法:
引入 js <script src="./particle-line.js"></script> js 源碼地址
let point = new particleLine({
canvas: canvas, // canvas
width: 寬, // canvas寬
height: 高, // canvas高
num: 粒子數(shù), // 粒子數(shù)
isBg: true // true: 第一種效果 false: 第二種效果
});
point.play(); // 執(zhí)行
demo 鏈接:demo
參考博客:
5、總結(jié)
這個(gè) demo 實(shí)現(xiàn)起來還是比較簡單的,效果也挺不錯(cuò)的。難點(diǎn)應(yīng)該在于鼠標(biāo)粒子和普通粒子間的計(jì)算。canvas 粒子還有很多其他炫酷的效果,比如粒子變換特定形狀,用getImageData 方法獲取畫布指定矩形像素,通過獲取到的點(diǎn)位跟 RGBA 值用粒子繪畫出來。
6、了解更多
原文鏈接:canvas 粒子線條