是不是還蠻酷的呢?本文我們就來一點一點分析怎麼實現它!
分析
首先我們看看這個效果具體有那些要點。首先,這麼炫酷的效果肯定是要用到 Canvas 了,每個星星可以看作為一個粒子,因此,整個效果實際上就是粒子系統了。此外,我們可以發現每個粒子之間是相互連接的,只不過離的近的粒子之間的連線較粗且透明度較低,而離的遠的則相反。
開始 Coding
HTML 部分
這部分我就簡單放了一個 標籤,設置樣式使其填充全屏。
<canvas height="620" width="1360" id="canvas" style="position: absolute; height: 100%;"/>
然後為了讓所有元素沒有間距和內補,我還加了一條全局樣式:
* {
margin: 0;
padding: 0;
}
JavaScript 部分
下面我們來寫核心的代碼。首先我們要得到那個 canvas 並得到繪製上下文:
var canvasEl = document.getElementById('canvas');
var ctx = canvasEl.getContext('2d');
var mousePos = [0, 0];
緊接著我們聲明兩個變量,分別用於存儲「星星」和邊:
var nodes = [];
var edges = [];
下一步,我們做些準備工作,就是讓畫布在窗口大小發生變化時重新繪製,並且調整自身解析度:
window.onresize = function () {
canvasEl.width = document.body.clientWidth;
canvasEl.height = canvasEl.clientHeight;
if (nodes.length == 0) {
constructNodes();
}
render();
};
window.onresize(); // trigger the event manually.
我們在第一次修改大小後構建了所有節點,這裡就要用到下一個函數(constructNodes)了
這個函數中我們隨機創建幾個點,我們用字典對象的方式存儲這些點的各個信息:
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);
});
});
}
為了實現後面一個更炫酷的效果,我給第一個點加了一個 drivenByMouse 屬性,這個點的位置不會被粒子系統管理,也不會繪製出來,但是它會與其他點連線,這樣就實現了滑鼠跟隨的效果了。
這裡稍微解釋一下 radius 屬性的取值,我希望讓絕大部分點都是小半徑的,而極少數的點半徑比較大,所以我這裡用了一點小 tricky,就是用概率控制點的半逕取值,不斷調整這個概率閾值就能獲取期待的半徑隨機分布。
點都構建完畢了,就要構建點與點之間的連線了,我們用到雙重遍歷,把兩個點捆綁成一組,放到 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;
}
看到這麼一大段代碼不要害怕,其實做的事情很簡單。這是粒子系統的核心,就是遍歷粒子,並且更新其狀態。更新的公式就是
v = v + a
s = s + v
a是加速度,v是速度,s是位移。由於我們這裡不涉及加速度,所以就不寫了。然後我們需要作一個邊緣的碰撞檢測,不然我們的「星星」都無拘無束地一點點飛~走~了~。邊緣碰撞後的處理方式就是讓速度矢量反轉,這樣粒子就會「掉頭」回來。
還記得我們需要做的滑鼠跟隨嗎?也在這處理,我們讓第一個點的位置一點一點移動到滑鼠的位置,下面這個公式很有意思,可以輕鬆實現緩動:
x = x + (t - x) / factor
其中 factor 是緩動因子,t 是最終位置,x 是當前位置。至於這個公式的解釋還有個交互大神 Bret Victor 在他的演講中提到過,視頻做的非常好,有條(ti)件(zi)大家一定要看看:Bret Victor – Stop Drawing Dead Fish
好了,回到主題。我們在上面的函數中處理完了一幀中的數據,我們要讓整個粒子系統連續地運轉起來就需要一個timer了,但是十分不提倡大家使用 setInterval,而是儘可能使用 requestAnimationFrame,它能保證你的幀率鎖定在
剩下的就是繪製啦:
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();
});
}
常規的 Canvas 繪圖操作,注意 beginPath 一定要調用,不然你的線就全部穿在一起了… 需要說明的是,在繪製邊的時候,我們先要計算兩點距離,然後根據一個閾值來判斷是否要繪製這條邊,這樣我們才能實現距離遠的點之間連線不可見的效果。
到這裡,我們的整個效果就完成了。如果不明白大家也可以去我文章開頭放的 repo 離去看完整的源碼。Have fun!!
源自:http://www.jianshu.com/p/f5c0f9c4bc39
聲明:文章著作權歸作者所有,如有侵權,請聯繫小編刪除。