Створюємо анімовану мережу з динамічних точок за допомогою HTML5 Canvas

36

Від автора: демо нижче – можливо, один із найбільш упізнаваних візуальних мотивів останніх років. Не знаю, як правильно назвати цей ефект, це анімація мережі динамічних точок. Це мій варіант даної анімації, за основу було взято демо Деніела Майовски.

Найчастіше така анімація накладається як фон для іншого контенту, тобто її розмір повинен збігатися з розміром вікна браузера:

body {
background: #222;
margin: 0rem;
min-height: 100vh;
}
#canvas {
position: absolute;
display: block;
top: 0;
left: 0;
z-index: -1;
}

У Codepen версії коду вище трохи більше стилів. Там показано, як помістити текст поверх анімації. Щоб тег canvas завжди займав всю висоту і ширину вікна, насамперед необхідно написати JS скрипт в кінці сторінки з функцією resize:

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,
}

Щоб надати точках мережі випадковий розмір і напрямок руху використовуються параметри швидкості і радіуса. Параметр linkRadius визначає відстань, на якому найближчі точки з’єднуються прямою.

Розміри тега canvas необхідно змінювати таким чином, щоб точки завжди стосувалися країв вікна браузера. Функція resizeReset() викликається один раз після повного завантаження скрипта. При зміні розмірів вікна її необхідно викликати повторно так, щоб вона не уповільнювала скрипт:

let delay = 200, tid;
window.addEventListener. («resize», function(){
deBouncer();
});
let deBouncer = function() {
clearTimeout(tid);
tid = setTimeout(function() {
resizeReset();
}, delay);
};

Об’єкт Particle являє собою досить велику функцію, яка генерує всі точки:

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 = h || this.y 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.vector зберігається напрямок руху точки. Якщо this.vector.x дорівнює 1, частинка рухається вправо, якщо -1, то вліво. Точно так само якщо this.vector.y має від’ємне значення, точка рухається вгору, якщо позитивний – вниз;

this.update обчислює наступні координати для всіх точок. Спочатку перевіряється, чи торкнулася точка кордону. Якщо точка проходить кордон, вектор руху множиться на -1, тим самим напрямок змінюється на протилежне;

при зміні розміру вікна частинка може вивалитися з периметра охоплення функції border. Для запобігання таких ситуацій задіяний ряд умов if, які в разі догляду за периметр заново задають позицію точки в межах полотна;

в кінці отрісовиваємих сама точка.

Код нижче приводить все в рух:

function setup(){
particles = [];
for (let i = 0; i < opts.particleAmount; i++){
particles.push( new Particle() );
}
window.requestAnimationFrame(loop);
}

Функція setup створює масив particles і з допомогою requestionAnimationFrame заповнює його точками перед викликом функції loop. Функція loop:

function loop(){
window.requestAnimationFrame(loop);
drawArea.clearRect(0,0,w,h);
for (let i = 0; i < particles.length; i++){
particles.update();
particles.draw();
}
}

Функція loop очищає полотно, оновлює позиції всіх точок і малює їх. Постійне оновлення полотна за допомогою функції requestAnimationFrame() створює відчуття анімації.

Все починається з виклику функції setup(), але спочатку задається парочка констант і перемінних і ініціалізується функція resizeReset:

const canvasBody = document.getElementById(«canvas»),
drawArea = canvasBody.getContext(«2d»);
let delay = 200, tid;
resizeReset();
setup();

На даному етапі ми побачимо на екрані рухаються точки:

Щоб додати сітку, потрібно написати додатковий код.

Створюємо лінії

Щоб намалювати лінії, у функцію loop() потрібно додати наступний код:

function loop(){
window.requestAnimationFrame(loop);
drawArea.clearRect(0,0,w,h);
for (let i = 0; i < particles.length; i++){
particles.update();
particles.draw();
}
for (let i = 0; i < particles.length; i++){
linkPoints(particles, particles);
}
}

Функція linkPoints викликається для кожної точки. Ця функція також використовує шматок коду нижче:

let checkDistance = function(x1, y1, x2, y2){
return Math.sqrt(Math.pow(x2 — x1, 2) + Math.pow(y2 — y1, 2));
};

Функція checkDistance визначає відстань між всіма точками. Якщо воно менше linkDistance, то прозорість лінії буде більше 0, тобто між точками виникне лінія.

Перед тим, як ми продовжимо, rgb колір потрібно розбити на компоненти:

let rgb = opts.lineColor.match(/\d+/g);

Функція linkPoints перевіряє всі точки по відношенню один до одного і з допомогою шаблонних рядків малює лінії з певним рівнем прозорості:

let linkPoints = function(point1, hubs){
for (let i = 0; i < hubs.length; i++) {
let distance = checkDistance(point1.x, point1.y, hubs.x, hubs.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.x, hubs.y);
drawArea.closePath();
drawArea.stroke();
}
}
}

Висновок

Результат вийшов вражаючий, раджу поекспериментувати з різними значеннями в скрипті.

Варто відзначити, що якщо додати занадто багато точок або занадто сильно збільшити відстань для зв’язку (що створить надто багато ліній), анімація може зламатися. Було б непогано, якби при звуженні вікна браузера знижувалася швидкість точок. Якщо зменшити площу, точки почнуть рухатися дуже швидко.

CodePen демо до цієї статті https://codepen.io/dudleystorey/pen/NbNjjX