От автора: демо ниже – возможно, один из самых узнаваемых визуальных мотивов последних лет. Не знаю, как правильно назвать этот эффект, это анимация сети динамических точек. Это мой вариант данной анимации, за основу было взято демо Дэниела Майовски.
Зачастую такая анимация накладывается как фон для другого контента, то есть ее размер должен совпадать с размером окна браузера:
1 |
<canvas id="canvas"></canvas> |
1 2 3 4 5 6 7 8 9 10 11 12 |
body { background: #222; margin: 0rem; min-height: 100vh; } #canvas { position: absolute; display: block; top: 0; left: 0; z-index: -1; } |
В Codepen версии кода выше чуть больше стилей. Там показано, как поместить текст поверх анимации. Чтобы тег canvas всегда занимал всю высоту и ширину окна, первым делом необходимо написать JS скрипт в конце страницы с функцией resize:
1 2 3 4 |
let resizeReset = function() { w = canvasBody.width = window.innerWidth; h = canvasBody.height = window.innerHeight; } |
Создаем точки
В объекте opts хранятся свойства со значениями по умолчанию для скрипта:
1 2 3 4 5 6 7 8 9 10 |
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() вызывается единожды после полной загрузки скрипта. При изменении размеров окна ее необходимо вызывать повторно так, чтобы она не замедляла скрипт:
1 2 3 4 5 6 7 8 9 10 11 12 |
let delay = 200, tid; window.addEventListener("resize", function(){ deBouncer(); }); let deBouncer = function() { clearTimeout(tid); tid = setTimeout(function() { resizeReset(); }, delay); }; |
Объект Particle представляет собой достаточно большую функцию, генерирующую все точки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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.vector хранится направление движения точки. Если this.vector.x равен 1, частичка движется вправо, если -1, то влево. Точно так же если this.vector.y имеет отрицательное значение, точка движется вверх, если положительное – вниз;
this.update вычисляет следующие координаты для всех точек. Сперва проверяется, коснулась ли точка границы. Если точка проходит границу, вектор движения умножается на -1, тем самым направление меняется на противоположное;
при изменении размера окна частичка может вывалиться из периметра охвата функции border. Для предотвращения таких ситуаций задействован ряд условий if, которые в случае ухода за периметр заново задают позицию точки в пределах холста;
в конце отрисовывается сама точка.
Код ниже приводит все в движение:
1 2 3 4 5 6 7 |
function setup(){ particles = []; for (let i = 0; i < opts.particleAmount; i++){ particles.push( new Particle() ); } window.requestAnimationFrame(loop); } |
Функция setup создает массив particles и с помощью requestionAnimationFrame заполняет его точками перед вызовом функции loop. Функция loop:
1 2 3 4 5 6 7 8 |
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 очищает холст, обновляет позиции всех точек и рисует их. Постоянное обновление холста с помощью функции requestAnimationFrame() создает ощущение анимации.
Все начинается с вызова функции setup(), но сначала задается парочка констант и переменных и инициализируется функция resizeReset:
1 2 3 4 5 |
const canvasBody = document.getElementById("canvas"), drawArea = canvasBody.getContext("2d"); let delay = 200, tid; resizeReset(); setup(); |
На данном этапе мы увидим на экране движущиеся точки:
Чтобы добавить сетку, нужно написать дополнительный код.
Создаем линии
Чтобы нарисовать линии, в функцию loop() нужно добавить следующий код:
1 2 3 4 5 6 7 8 9 10 11 |
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 вызывается для каждой точки. Эта функция также использует кусок кода ниже:
1 2 3 |
let checkDistance = function(x1, y1, x2, y2){ return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); }; |
Функция checkDistance определяет расстояние между всеми точками. Если оно меньше linkDistance, то прозрачность линии будет больше 0, то есть между точками возникнет линия.
Перед тем как мы продолжим, rgb цвет нужно разбить на компоненты:
1 |
let rgb = opts.lineColor.match(/\d+/g); |
Функция linkPoints проверяет все точки по отношению друг к другу и с помощью шаблонных строк отрисовывает линии с определенным уровнем прозрачности:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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(); } } } |
Заключение
Результат получился впечатляющий, советую поэкспериментировать с разными значениями в скрипте.
Стоит отметить, что если добавить слишком много точек или слишком сильно увеличить расстояние для связи (что создаст слишком много линий), анимация может сломаться. Было бы неплохо, если бы при сужении окна браузера снижалась скорость точек. Если уменьшить площадь, точки начнут двигаться слишком быстро.
CodePen демо к этой статье //codepen.io/dudleystorey/pen/NbNjjX
Источник: //thenewcode.com/
Редакция: Команда webformyself.