164 lines
5.4 KiB
JavaScript
164 lines
5.4 KiB
JavaScript
/**
|
|
* Ripple Waves Background - Versión con Distorsión de Ruido Topográfico
|
|
* Reemplaza o actualiza tu archivo ripple-bg.js
|
|
*/
|
|
(() => {
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initRipple);
|
|
} else {
|
|
initRipple();
|
|
}
|
|
|
|
function initRipple() {
|
|
const canvas = document.getElementById('ripple-canvas');
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
const parent = canvas.parentElement;
|
|
|
|
// =========================================================================
|
|
// PARÁMETROS CONFIGURABLES
|
|
// =========================================================================
|
|
const CONFIG = {
|
|
spacing: 36, // Separación entre anillos
|
|
speed: 0.5, // Velocidad de expansión hacia afuera
|
|
lineWidth: 2, // Grosor de las líneas
|
|
maxAlpha: 0.42, // Opacidad máxima
|
|
centerOffsetX: 0.5, // Centro horizontal (50%)
|
|
centerOffsetY: 0.2, // Centro vertical (alineado con el h2)
|
|
|
|
// --- PARÁMETROS DE RUIDO / DISTORSIÓN ---
|
|
distortionStrength: 14, // Qué tanto se deforma el círculo (en px)
|
|
distortionFrequency: 3, // Cantidad de ondulaciones en el perímetro
|
|
evolutionSpeed: 0.00001, // Velocidad con la que muta la forma de las ondas
|
|
segmentsPerRing: 25, // Calidad/resolución del contorno (70 a 100)
|
|
|
|
cssVar: '--ripple-color', // Variable de tu CSS
|
|
fallbackColor: 'rgba(24, 123, 141, 0.4)'
|
|
};
|
|
|
|
let width = 0;
|
|
let height = 0;
|
|
let dpr = 1;
|
|
let maxRadius = 0;
|
|
let offset = 0;
|
|
let time = 0;
|
|
let currentColor = CONFIG.fallbackColor;
|
|
|
|
// --- Leer variable de color desde CSS ---
|
|
function updateThemeColor() {
|
|
const color = getComputedStyle(document.documentElement)
|
|
.getPropertyValue(CONFIG.cssVar)
|
|
.trim();
|
|
currentColor = color || CONFIG.fallbackColor;
|
|
}
|
|
|
|
updateThemeColor();
|
|
|
|
const observer = new MutationObserver(updateThemeColor);
|
|
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme'] });
|
|
observer.observe(document.body, { attributes: true, attributeFilter: ['class', 'data-theme'] });
|
|
if (window.matchMedia) {
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateThemeColor);
|
|
}
|
|
|
|
// --- Adaptación de resolución Retina / HiDPI ---
|
|
function resize() {
|
|
dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
width = parent.clientWidth;
|
|
height = parent.clientHeight;
|
|
|
|
canvas.width = width * dpr;
|
|
canvas.height = height * dpr;
|
|
ctx.scale(dpr, dpr);
|
|
|
|
const cx = width * CONFIG.centerOffsetX;
|
|
const cy = height * CONFIG.centerOffsetY;
|
|
const dx = Math.max(cx, width - cx);
|
|
const dy = Math.max(cy, height - cy);
|
|
maxRadius = Math.sqrt(dx * dx + dy * dy) + CONFIG.spacing * 2;
|
|
}
|
|
|
|
resize();
|
|
|
|
if (window.ResizeObserver) {
|
|
new ResizeObserver(resize).observe(parent);
|
|
} else {
|
|
window.addEventListener('resize', resize);
|
|
}
|
|
|
|
// --- Función de Pseudo-Ruido Armónico ---
|
|
// Combina ondas senoidales a distintas frecuencias para simular turbulencia/ruido suave
|
|
function getNoiseDeformation(angle, baseRadius, t) {
|
|
const f = CONFIG.distortionFrequency;
|
|
const w1 = Math.sin(angle * f + t + baseRadius * 0.015);
|
|
const w2 = Math.cos(angle * (f + 2) - t * 0.8 + baseRadius * 0.02);
|
|
const w3 = Math.sin(angle * 2 + t * 0.4);
|
|
|
|
// Crece ligeramente a medida que el anillo viaja hacia afuera
|
|
const growthFactor = 0.5 + (baseRadius / maxRadius) * 0.5;
|
|
|
|
return (w1 * 0.55 + w2 * 0.35 + w3 * 0.1) * CONFIG.distortionStrength * growthFactor;
|
|
}
|
|
|
|
// --- Loop de Animación ---
|
|
function render() {
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
const cx = width * CONFIG.centerOffsetX;
|
|
const cy = height * CONFIG.centerOffsetY;
|
|
|
|
offset = (offset + CONFIG.speed) % CONFIG.spacing;
|
|
time += CONFIG.evolutionSpeed * 1000;
|
|
|
|
ctx.save();
|
|
ctx.strokeStyle = currentColor;
|
|
ctx.lineWidth = CONFIG.lineWidth;
|
|
|
|
const angleStep = (Math.PI * 2) / CONFIG.segmentsPerRing;
|
|
|
|
// Dibujar cada anillo deformado
|
|
for (let r = offset; r < maxRadius; r += CONFIG.spacing) {
|
|
if (r <= 2) continue;
|
|
|
|
const progress = r / maxRadius;
|
|
|
|
// Desvanecimiento suave en centro y bordes
|
|
let alpha = 1;
|
|
if (progress < 0.12) {
|
|
alpha = progress / 0.12;
|
|
} else {
|
|
alpha = Math.cos(((progress - 0.12) / 0.88) * (Math.PI / 2));
|
|
}
|
|
|
|
ctx.globalAlpha = Math.max(0, Math.min(1, alpha * CONFIG.maxAlpha));
|
|
|
|
ctx.beginPath();
|
|
|
|
// Trazar el contorno deformado punto a punto
|
|
for (let i = 0; i <= CONFIG.segmentsPerRing; i++) {
|
|
const angle = i * angleStep;
|
|
const deformation = getNoiseDeformation(angle, r, time);
|
|
const currentRadius = Math.max(0, r + deformation);
|
|
|
|
const px = cx + Math.cos(angle) * currentRadius;
|
|
const py = cy + Math.sin(angle) * currentRadius;
|
|
|
|
if (i === 0) {
|
|
ctx.moveTo(px, py);
|
|
} else {
|
|
ctx.lineTo(px, py);
|
|
}
|
|
}
|
|
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
requestAnimationFrame(render);
|
|
}
|
|
|
|
requestAnimationFrame(render);
|
|
}
|
|
})(); |