238 lines
9.2 KiB
JavaScript
238 lines
9.2 KiB
JavaScript
/* ==========================================
|
||
Phantom Protocol - Advanced Background Animation
|
||
DHT Network visualization with nodes and connections
|
||
Based on Hero section animation
|
||
© NeroWorld AI, 2025
|
||
========================================== */
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Конфигурация
|
||
const config = {
|
||
nodeCount: 60, // Количество узлов DHT
|
||
connectionDistance: 180, // Расстояние для связей
|
||
nodeSpeedMin: 0.2, // Минимальная скорость
|
||
nodeSpeedMax: 0.8, // Максимальная скорость
|
||
nodeRadiusMin: 1.5, // Минимальный размер узла
|
||
nodeRadiusMax: 3, // Максимальный размер узла
|
||
colors: {
|
||
node: 'rgba(0, 240, 255, ', // Цвет узлов (cyan) + opacity
|
||
connection: 'rgba(122, 62, 255, ', // Цвет связей (purple) + opacity
|
||
glow: 'rgba(0, 240, 255, 0.5)' // Цвет свечения
|
||
}
|
||
};
|
||
|
||
// Класс узла DHT сети
|
||
class DHTNode {
|
||
constructor(canvas) {
|
||
this.canvas = canvas;
|
||
this.reset();
|
||
}
|
||
|
||
reset() {
|
||
this.x = Math.random() * this.canvas.width;
|
||
this.y = Math.random() * this.canvas.height;
|
||
this.vx = (Math.random() - 0.5) * (config.nodeSpeedMin + Math.random() * (config.nodeSpeedMax - config.nodeSpeedMin));
|
||
this.vy = (Math.random() - 0.5) * (config.nodeSpeedMin + Math.random() * (config.nodeSpeedMax - config.nodeSpeedMin));
|
||
this.radius = config.nodeRadiusMin + Math.random() * (config.nodeRadiusMax - config.nodeRadiusMin);
|
||
this.opacity = Math.random() * 0.5 + 0.5; // 0.5 - 1.0
|
||
}
|
||
|
||
update() {
|
||
this.x += this.vx;
|
||
this.y += this.vy;
|
||
|
||
// Отскок от границ
|
||
if (this.x < 0 || this.x > this.canvas.width) {
|
||
this.vx *= -1;
|
||
this.x = Math.max(0, Math.min(this.canvas.width, this.x));
|
||
}
|
||
if (this.y < 0 || this.y > this.canvas.height) {
|
||
this.vy *= -1;
|
||
this.y = Math.max(0, Math.min(this.canvas.height, this.y));
|
||
}
|
||
}
|
||
|
||
draw(ctx) {
|
||
ctx.beginPath();
|
||
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
||
ctx.fillStyle = config.colors.node + this.opacity + ')';
|
||
ctx.fill();
|
||
|
||
// Добавляем свечение для больших узлов
|
||
if (this.radius > 2) {
|
||
ctx.shadowBlur = 15;
|
||
ctx.shadowColor = config.colors.glow;
|
||
ctx.fill();
|
||
ctx.shadowBlur = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Класс анимации DHT сети
|
||
class DHTNetworkAnimation {
|
||
constructor(canvasId) {
|
||
this.canvas = document.getElementById(canvasId);
|
||
if (!this.canvas) {
|
||
console.warn('Canvas element not found:', canvasId);
|
||
return;
|
||
}
|
||
|
||
this.ctx = this.canvas.getContext('2d');
|
||
this.nodes = [];
|
||
this.animationId = null;
|
||
this.resizeTimeout = null;
|
||
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
// Установка размера canvas
|
||
this.resize();
|
||
|
||
// Создание узлов DHT
|
||
for (let i = 0; i < config.nodeCount; i++) {
|
||
this.nodes.push(new DHTNode(this.canvas));
|
||
}
|
||
|
||
// Обработчик изменения размера окна с throttling
|
||
window.addEventListener('resize', () => {
|
||
if (this.resizeTimeout) clearTimeout(this.resizeTimeout);
|
||
this.resizeTimeout = setTimeout(() => this.resize(), 150);
|
||
});
|
||
|
||
// Запуск анимации
|
||
this.animate();
|
||
}
|
||
|
||
resize() {
|
||
if (!this.canvas) return;
|
||
|
||
const newWidth = window.innerWidth;
|
||
const newHeight = Math.max(
|
||
document.documentElement.scrollHeight,
|
||
document.body.scrollHeight,
|
||
document.documentElement.clientHeight,
|
||
window.innerHeight
|
||
);
|
||
|
||
// Обновляем только если размер изменился
|
||
if (this.canvas.width !== newWidth || this.canvas.height !== newHeight) {
|
||
this.canvas.width = newWidth;
|
||
this.canvas.height = newHeight;
|
||
|
||
// Переинициализируем узлы при большом изменении размера
|
||
if (Math.abs(this.canvas.width - newWidth) > 100) {
|
||
this.nodes.forEach(node => node.reset());
|
||
}
|
||
}
|
||
}
|
||
|
||
drawConnections() {
|
||
const { connectionDistance, colors } = config;
|
||
|
||
for (let i = 0; i < this.nodes.length; i++) {
|
||
for (let j = i + 1; j < this.nodes.length; j++) {
|
||
const dx = this.nodes[i].x - this.nodes[j].x;
|
||
const dy = this.nodes[i].y - this.nodes[j].y;
|
||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||
|
||
if (distance < connectionDistance) {
|
||
// Opacity зависит от расстояния (ближе = ярче)
|
||
const opacity = (1 - distance / connectionDistance) * 0.4;
|
||
|
||
this.ctx.beginPath();
|
||
this.ctx.moveTo(this.nodes[i].x, this.nodes[i].y);
|
||
this.ctx.lineTo(this.nodes[j].x, this.nodes[j].y);
|
||
this.ctx.strokeStyle = colors.connection + opacity + ')';
|
||
this.ctx.lineWidth = 1;
|
||
this.ctx.stroke();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
animate() {
|
||
if (!this.canvas || !this.ctx) return;
|
||
|
||
// Очистка canvas
|
||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||
|
||
// Рисуем связи первыми (за узлами)
|
||
this.drawConnections();
|
||
|
||
// Обновление и отрисовка узлов
|
||
this.nodes.forEach(node => {
|
||
node.update();
|
||
node.draw(this.ctx);
|
||
});
|
||
|
||
// Следующий кадр
|
||
this.animationId = requestAnimationFrame(() => this.animate());
|
||
}
|
||
|
||
destroy() {
|
||
if (this.animationId) {
|
||
cancelAnimationFrame(this.animationId);
|
||
this.animationId = null;
|
||
}
|
||
if (this.resizeTimeout) {
|
||
clearTimeout(this.resizeTimeout);
|
||
}
|
||
this.nodes = [];
|
||
}
|
||
}
|
||
|
||
// Автоматическая инициализация при загрузке
|
||
function initBackground() {
|
||
// Проверяем что DOM загружен
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', startAnimation);
|
||
} else {
|
||
startAnimation();
|
||
}
|
||
|
||
function startAnimation() {
|
||
// Ждем полной загрузки страницы
|
||
if (document.readyState === 'complete') {
|
||
createAnimation();
|
||
} else {
|
||
window.addEventListener('load', createAnimation);
|
||
}
|
||
}
|
||
|
||
function createAnimation() {
|
||
setTimeout(() => {
|
||
// Создаем анимацию
|
||
window.phantomBackground = new DHTNetworkAnimation('phantomBackground');
|
||
|
||
// Отслеживание изменений DOM (с throttling)
|
||
if (window.phantomBackground) {
|
||
let mutationTimeout;
|
||
const observer = new MutationObserver(() => {
|
||
if (mutationTimeout) clearTimeout(mutationTimeout);
|
||
mutationTimeout = setTimeout(() => {
|
||
if (window.phantomBackground && typeof window.phantomBackground.resize === 'function') {
|
||
window.phantomBackground.resize();
|
||
}
|
||
}, 1000); // Задержка 1 секунда для стабильности
|
||
});
|
||
|
||
// Наблюдаем за изменениями структуры страницы
|
||
observer.observe(document.body, {
|
||
childList: true,
|
||
subtree: false
|
||
});
|
||
}
|
||
|
||
console.log('✨ DHT Network Background initialized');
|
||
}, 100);
|
||
}
|
||
}
|
||
|
||
// Запуск
|
||
initBackground();
|
||
|
||
})();
|