使用HTML5 Canvas創建動態粒子網格動畫
最近幾年,辨識度最高的虛擬圖案之一也許就是上面你所看到的這種動畫形式了;我不確定這種動畫形式是否有學名,這里我姑且稱之為 動態粒子網格動畫(dynamic point mesh animation) 。本例中的動畫是基于這種動畫形式的變種,靈感來自于 Daniel Mayovskiy 之前的作品。
通常,這類動畫被放置于其他內容之后,所以在該例中,將 <canvas> 的大小設置為與視口相同的大小:
<canvas id="canvas"></canvas>
樣式如下:
body {
background: #222;
margin: 0rem;
min-height: 100vh;
}
#canvas {
position: absolute;
display: block;
top: 0;
left: 0;
z-index: -1;
}
這段代碼的Codepen版本 在動畫的頂部放置了一些文本,并為文本添加了些許結構和樣式。
為了確保 <canvas> 能夠鋪滿視口,頁面末尾的第一部分 JavaScript 代碼是一個縮放函數:
let resizeReset = function() {
w = canvasBody.width = window.innerWidth;
h = canvasBody.height = window.innerHeight;
}
創建點集
opts 是一個 對象 ,包含一系列屬性,默認屬性如下:
const opts = {
particleColor: "rgb(200,200,200)",
lineColor: "rgb(200,200,200)",
particleAmount: 40,
defaultSpeed: 1,
variantSpeed: 1,
defaultRadius: 2,
variantRadius: 2,
linkRadius: 200,
}
變量 variantSpeed 和 variantRadiut 用來增加點的尺寸大小和移動速度的 隨機性 , linkRadius 表示點與點要連成線必須要靠近的距離范圍。
<canvas> 元素可以被縮放,這樣就會導致粒子會碰觸到瀏覽器窗口的邊緣。腳本一加載, resizeReset() 函數就會被調用,但是需要延遲或“去抖”,以便在頁面其余部分的操作期間不會減慢腳本的運行:
let delay = 200, tid;
window.addEventListener("resize", function(){
deBouncer();
});
let deBouncer = function() {
clearTimeout(tid);
tid = setTimeout(function() {
resizeReset();
}, delay);
};
生成每個點(each of the dots)的 粒子 對象是一個比較龐大的函數:
Particle = function(xPos, yPos){
this.x = Math.random() * w;
this.y = Math.random() * h;
this.speed = opts.defaultSpeed + Math.random() * opts.variantSpeed;
this.directionAngle = Math.floor(Math.random() * 360);
this.color = opts.particleColor;
this.radius = opts.defaultRadius + Math.random() * opts. variantRadius;
this.vector = {
x: Math.cos(this.directionAngle) * this.speed,
y: Math.sin(this.directionAngle) * this.speed
};
this.update = function(){
this.border();
this.x += this.vector.x;
this.y += this.vector.y;
};
this.border = function(){
if (this.x >= w || this.x <= 0) {
this.vector.x *= -1;
}
if (this.y >= h || this.y <= 0) {
this.vector.y *= -1;
}
if (this.x > w) this.x = w;
if (this.y > h) this.y = h;
if (this.x < 0) this.x = 0;
if (this.y < 0) this.y = 0;
};
this.draw = function(){
drawArea.beginPath();
drawArea.arc(this.x, this.y, this.radius, 0, Math.PI*2);
drawArea.closePath();
drawArea.fillStyle = this.color;
drawArea.fill();
};
};
在腳本的上下文中, this 指代的是每個創建出來的 粒子 :
-
每個粒子的初始速度和角度是隨機生成的,粒子的顏色通過相關的設置選項來確定。
-
this.vector 用來存儲粒子的 移動方向 :如果 this.vector.x 為 1 ,則粒子向 右 運動;如果是 -1 ,則粒子向 左 移動。同樣,如果 this.vector.y 為 負 ,則粒子向 上 移動,如果為 正 ,則粒子向 下 移動。
-
this.update 用來更新每個粒子 下一個位置 的坐標。首先,進行邊緣檢測;如果粒子的移動超出了canvas的尺寸,則將方向向量乘以 -1 產生 反向 的運動方向。
-
窗口縮放可能會引起粒子超出邊界,如此一來 邊緣檢測 函數就捕捉不到了,所以就需要一系列的 if 語句來檢測這種情況,將粒子的位置重置為當前canvas的邊界。
-
最后一步,將這些點繪制到畫布上。
我們需要進行下面的操作來使粒子運動起來:
function setup(){
particles = [];
for (let i = 0; i < opts.particleAmount; i++){
particles.push( new Particle() );
}
window.requestAnimationFrame(loop);
}
在調用 loop 函數之前, setup 函數會創建一個由一系列粒子組成的 粒子 數組,這里使用 requestionAnimationFrame 來創建循環動畫。
loop 函數看起來是這個樣子的:
function loop(){
window.requestAnimationFrame(loop);
drawArea.clearRect(0,0,w,h);
for (let i = 0; i < particles.length; i++){
particles[i].update();
particles[i].draw();
}
}
loop 函數會清除 canvas 畫布,更新每個粒子的位置,然后再繪制粒子;這里使用 requestAnimationFrame() 來刷新、創建動畫。
設置了相關常量、變量,并初始化了 resizeReset 函數后,調用 setup 函數開始運行動畫:
const canvasBody = document.getElementById("canvas"),
drawArea = canvasBody.getContext("2d");
let delay = 200, tid;
resizeReset();
setup();
到了這一步,動畫看起來就是一系列點在 canvas 畫布里面來回移動:
動畫效果可以去 CodePen 上查看Dudley Storey ( @dudleystorey )的 《使用HTML5的canvas創建動態點動畫》 。
為了創建網狀結構,我們需要增加一點代碼。
創建線條
為了繪制線條, loop() 函數需要添加些代碼,變成下面這個樣子:
function loop(){
window.requestAnimationFrame(loop);
drawArea.clearRect(0,0,w,h);
for (let i = 0; i < particles.length; i++){
particles[i].update();
particles[i].draw();
}
for (let i = 0; i < particles.length; i++){
linkPoints(particles[i], particles);
}
}
每個粒子都會調用 linkPoints 函數,這個函數會調用 checkDistance 函數:
let checkDistance = function(x1, y1, x2, y2){
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
};
checkDistance 函數決定了每個點與點之間的距離;如果距離小于 checkDistance ,那么線條的 透明度 就會被設置成大于0,并且線條會在匹配的點之間重新繪制。
在繼續之前,先分解下 rgb 顏色 :
`let rgb = opts.lineColor.match(/\d+/g);``
linkPoints 函數用來檢測每一個點相對于剩下其他點的距離(等同于函數的"hubs"參數),然后使用 模板字面量 按照相應的 透明度 來繪制線條:
let linkPoints = function(point1, hubs){
for (let i = 0; i < hubs.length; i++) {
let distance = checkDistance(point1.x, point1.y, hubs[i].x, hubs[i].y);
let opacity = 1 - distance / opts.linkRadius;
if (opacity > 0) {
drawArea.lineWidth = 0.5;
drawArea.strokeStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opacity})`;
drawArea.beginPath();
drawArea.moveTo(point1.x, point1.y);
drawArea.lineTo(hubs[i].x, hubs[i].y);
drawArea.closePath();
drawArea.stroke();
}
}
}
結論
使用ES6是非常高效的,我也鼓勵你在腳本上去嘗試各個設置選項。
需要指出的是:如果添加過多的點和/或過多的連接距離(連接距離會創建過多的線條),動畫也會扛不住。當視口變窄時最好降低粒子的運動速度:粒子的尺寸越小,在愈加狹窄空間內的移動速度貌似會越快。
來自:http://www.zcfy.cc/article/the-new-code-create-a-dynamic-point-mesh-animation-with-html5-canvas-1780.html