關於這個效果我第一次見是在
https://www.mengxiaozhu.cn/
後來知乎登入頁也開始用了
https://www.zhihu.com/
網路上還有很多地方都在用,效果還是不錯的。
我見了之後覺得挺有意思的就研究了一下原理
下面開始coding:
先寫個canvas
標籤
<canvas height="620" width="1360" id="canvas"></canvas>
複製程式碼
加上一些預設的樣式:
*{
margin:0;
padding:0;
}
body{
overflow: hidden;
}
複製程式碼
這裡的overflow:hidden
是為了防止出現滾動條
下面開始寫JS:
首先我們要得到那個 canvas 並得到繪製上下文:
var canvasEl = document.getElementById('canvas');
var ctx = canvasEl.getContext('2d');
var mousePos = [0, 0];
複製程式碼
緊接著我們宣告兩個變數,分別用於儲存“星星”和邊:
var nodes = [];
var edges = [];
複製程式碼
然後我們定義一些其他的變數: var easingFactor = 5.0; //緩動因子 var backgroundColor = '#000'; //背景顏色 var nodeColor = '#fff'; //點顏色 var edgeColor = '#fff'; //邊顏色 var pageWidth = window.innerWidth, //視窗寬度 pageHeight = window.innerHeight; //視窗高度 設定畫布的大小鋪滿整個螢幕: window.onresize = function () { canvasEl.width = pageWidth; canvasEl.height = pageHeight;
if (nodes.length == 0) {
constructNodes();
}
render();
};
window.onresize();
複製程式碼
準備工作完成,我們要開始構建點了: function constructNodes() { for (var i = 0; i < 100; i++) { var node = { drivenByMouse: i == 0, x: Math.random() * canvasEl.width, y: Math.random() * canvasEl.height, vx: Math.random() * 1 - 0.5, vy: Math.random() * 1 - 0.5, radius: Math.random() > 0.9 ? 3 + Math.random() * 3 : 1 + Math.random() * 3 };
nodes.push(node);
}
nodes.forEach(function (e) {
nodes.forEach(function (e2) {
if (e == e2) {
return;
}
var edge = {
from: e,
to: e2
}
addEdge(edge);
});
});
}
複製程式碼
先建立100個點,每個點設定6個屬性,drivenByMouse屬性只有第一個點為true,其他的點為false,第一個點作為滑鼠跟隨點,不顯示出來,可以與其他點連線。x,y作為點的初始位置,取得是畫布內的隨機點,vx,vy表示點的初始速度,範圍為-0.5到0.5之間的隨機數,radius表示點的半徑,大部分的點為小的,少數的點為大的。
點都構建完畢了,就要構建點與點之間的連線了,我們用到雙重遍歷,把兩個點捆綁成一組,放到 edges 陣列中。注意這裡我用了另外一個函式來完成這件事,而沒有直接用 edges.push() ,為什麼?
假設我們之前連線了 A、B兩點,也就是外側迴圈是A,內側迴圈是B,那麼在下一次迴圈中,外側為B,內側為A,是不是也會建立一條邊呢?而實際上,這兩個邊除了方向不一樣以外是完全一樣的,這完全沒有必要而且佔用資源。因此我們在 addEdge 函式中進行一個判斷: function addEdge(edge) { var ignore = false;
edges.forEach(function (e) {
if (e.from == edge.from & e.to == edge.to) {
ignore = true;
}
if (e.to == edge.from & e.from == edge.to) {
ignore = true;
}
});
if (!ignore) {
edges.push(edge);
}
}
複製程式碼
至此,我們的準備工作就完畢了,下面我們要讓點動起來:
function step() {
nodes.forEach(function (e) {
if (e.drivenByMouse) {
return;
}
e.x += e.vx;
e.y += e.vy;
function clamp(min, max, value) {
if (value > max) {
return max;
} else if (value < min) {
return min;
} else {
return value;
}
}
if (e.x <= 0 || e.x >= canvasEl.width) {
e.vx *= -1;
e.x = clamp(0, canvasEl.width, e.x)
}
if (e.y <= 0 || e.y >= canvasEl.height) {
e.vy *= -1;
e.y = clamp(0, canvasEl.height, e.y)
}
});
adjustNodeDrivenByMouse();
render();
window.requestAnimationFrame(step);
}
function adjustNodeDrivenByMouse() {
nodes[0].x += (mousePos[0] - nodes[0].x) / easingFactor;
nodes[0].y += (mousePos[1] - nodes[0].y) / easingFactor;
}
複製程式碼
這段程式碼就是遍歷粒子,並且更新其狀態。根據一個簡單的物理公式 s = s + v
,每次執行都會 更新到點的下一步的狀態。
adjustNodeDrivenByMouse
函將第一個點作為滑鼠的跟隨點,easingFactor為緩動因子可以讓點的運動比滑鼠運動的稍慢一點。
然後我們要讓整個粒子系統連續地運轉起來就需要一個timer了,但是十分不提倡大家使用 setInterval,而是儘可能使用 requestAnimationFrame,它能保證你的幀率鎖定在當前瀏覽器的頻率下,一般為60HZ。
剩下的就是繪製了 function render() { ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvasEl.width, canvasEl.height);
edges.forEach(function (e) {
var l = lengthOfEdge(e);
var threshold = canvasEl.width / 8;
if (l > threshold) {
return;
}
ctx.strokeStyle = edgeColor;
ctx.lineWidth = (1.0 - l / threshold) * 2.5;
ctx.globalAlpha = 1.0 - l / threshold;
ctx.beginPath();
ctx.moveTo(e.from.x, e.from.y);
ctx.lineTo(e.to.x, e.to.y);
ctx.stroke();
});
ctx.globalAlpha = 1.0;
nodes.forEach(function (e) {
if (e.drivenByMouse) {
return;
}
ctx.fillStyle = nodeColor;
ctx.beginPath();
ctx.arc(e.x, e.y, e.radius, 0, 2 * Math.PI);
ctx.fill();
});
}
function lengthOfEdge(edge) {
return Math.sqrt(Math.pow((edge.from.x - edge.to.x), 2) + Math.pow((edge.from.y - edge.to.y), 2));
}
複製程式碼
繪製的時候我們要判斷線的長度如果大於某一個值,則不繪製該線了,如果在範圍之內粗細,與顏色的透明度都與線的長度相關,點除了第一個滑鼠跟隨點,其他的畫入即可。 最後加入滑鼠移動事件,啟動定時器: window.onmousemove = function (e) { mousePos[0] = e.clientX; mousePos1 = e.clientY; }
window.requestAnimationFrame(step);
複製程式碼
大功告成!!