125 lines
4.4 KiB
JavaScript
125 lines
4.4 KiB
JavaScript
(function () {
|
||
const CHARS = ['~', '-', '_', '·', '°', '*', 'o', '+', '×', '=', '≈', '∿', '◦'];
|
||
|
||
function pick(arr) {
|
||
return arr[Math.floor(Math.random() * arr.length)];
|
||
}
|
||
|
||
class Swirl {
|
||
constructor(container) {
|
||
this.container = container;
|
||
this.cx = container.offsetWidth / 2 || window.innerWidth / 2;
|
||
this.cy = container.offsetHeight / 2 || window.innerHeight / 2;
|
||
this.elements = [];
|
||
this.frameCount = 0;
|
||
this.angle = 0;
|
||
this.radius = 2;
|
||
this.hue = Math.random() * 360;
|
||
this.hueSpeed = 0.25;
|
||
this.angleStep = 0.18;
|
||
this.radiusStep = 0.55;
|
||
this.globalRotation = 0;
|
||
|
||
this.loop = this.loop.bind(this);
|
||
requestAnimationFrame(this.loop);
|
||
|
||
window.addEventListener('resize', () => {
|
||
this.cx = container.offsetWidth / 2 || window.innerWidth / 2;
|
||
this.cy = container.offsetHeight / 2 || window.innerHeight / 2;
|
||
});
|
||
}
|
||
|
||
createChar(x, y, hue, scale) {
|
||
const color = `hsl(${hue}, 60%, 68%)`;
|
||
const el = document.createElement('span');
|
||
el.textContent = pick(CHARS);
|
||
el.classList.add('swirl-char');
|
||
el.style.cssText =
|
||
`position:absolute;` +
|
||
`left:${x}px;top:${y}px;` +
|
||
`font-family:"Courier New",Courier,monospace;` +
|
||
`font-size:15px;line-height:1;` +
|
||
`color:${color};` +
|
||
`pointer-events:none;user-select:none;` +
|
||
`opacity:0.85;`;
|
||
el.style.transform = `scale(${scale}) rotate(${Math.random() * 360}deg)`;
|
||
el.style.display = 'inline-block';
|
||
|
||
this.container.appendChild(el);
|
||
this.elements.push({
|
||
el,
|
||
born: this.frameCount,
|
||
maxAge: 900 + Math.floor(Math.random() * 400),
|
||
});
|
||
}
|
||
|
||
ageElements() {
|
||
if (this.frameCount % 12 !== 0) return;
|
||
const now = this.frameCount;
|
||
let pruneCount = 0;
|
||
const maxPrune = 12;
|
||
for (let i = 0; i < this.elements.length && pruneCount < maxPrune; i++) {
|
||
const item = this.elements[i];
|
||
const age = now - item.born;
|
||
if (age >= item.maxAge) {
|
||
item.el.remove();
|
||
this.elements.splice(i, 1);
|
||
i--;
|
||
pruneCount++;
|
||
} else if (age > item.maxAge - 150) {
|
||
const fade = 1 - (age - (item.maxAge - 150)) / 150;
|
||
item.el.style.opacity = Math.max(0, fade * 0.85);
|
||
}
|
||
}
|
||
}
|
||
|
||
update() {
|
||
this.frameCount++;
|
||
|
||
// Rotation globale du tourbillon
|
||
this.globalRotation += 0.002;
|
||
|
||
// Évolution de la couleur
|
||
this.hue = (this.hue + this.hueSpeed) % 360;
|
||
|
||
// Ajouter un nouveau caractère à la spirale
|
||
if (this.frameCount % 2 === 0) {
|
||
this.angle += this.angleStep;
|
||
// L'espacement augmente avec le rayon (spirale qui s'écarte)
|
||
const dynamicStep = this.radiusStep + (this.radius / 100) * 0.18;
|
||
this.radius += dynamicStep;
|
||
|
||
const effectiveAngle = this.angle + this.globalRotation;
|
||
const x = this.cx + Math.cos(effectiveAngle) * this.radius;
|
||
const y = this.cy + Math.sin(effectiveAngle) * this.radius * 0.6;
|
||
|
||
// Scale qui grandit avec le rayon
|
||
const scale = 0.6 + (this.radius / 600) * 0.6;
|
||
|
||
this.createChar(x, y, this.hue, scale);
|
||
}
|
||
|
||
// Quand le rayon dépasse l'écran, on recommence plus petit
|
||
const maxRadius = Math.max(this.cx, this.cy) + 80;
|
||
if (this.radius > maxRadius) {
|
||
this.radius = 8 + Math.random() * 12;
|
||
this.hue = (this.hue + 60 + Math.random() * 120) % 360;
|
||
}
|
||
|
||
this.ageElements();
|
||
}
|
||
|
||
loop() {
|
||
this.update();
|
||
requestAnimationFrame(this.loop);
|
||
}
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const container = document.getElementById('swirl-container');
|
||
if (container) {
|
||
new Swirl(container);
|
||
}
|
||
});
|
||
})();
|