document.addEventListener('DOMContentLoaded', (event) => { // --- CONTROLLI PER L'ANIMAZIONE --- // Durata totale approssimativa dell'animazione per ogni elemento (in secondi) const totalDuration = 1; // Intervallo minimo e massimo tra i lampeggi di una parola (in secondi) const minBlinkInterval = 0.05; const maxBlinkInterval = 0.2; // Durata di ogni singolo lampeggio (quanto tempo una parola rimane visibile/invisibile) (in secondi) const blinkDuration = 0.1; // Opacità minima durante il lampeggio (0 = completamente invisibile) const minOpacity = 0.1; // --- FINE CONTROLLI --- // Seleziona tutti gli elementi con la classe 'blink' const targets = gsap.utils.toArray('.blink'); targets.forEach(target => { // Divide il testo in parole usando SplitType const typeSplit = new SplitType(target, { types: 'words' }); const words = typeSplit.words; // Array delle parole // Nascondi inizialmente le parole (opzionale, ma aiuta a prevenire flash iniziali) gsap.set(words, { opacity: 0 }); // Funzione per creare e avviare l'animazione per un elemento specifico const createAnimation = (element) => { // Crea una timeline GSAP per l'animazione di questo elemento const tl = gsap.timeline({ // Opzionale: aggiungi un leggero ritardo all'inizio // delay: 0.2, // Imposta la durata totale desiderata (GSAP la distribuirà) defaults: { duration: blinkDuration, ease: 'none' } }); // Array per tenere traccia dell'ordine casuale delle parole let shuffledWords = gsap.utils.shuffle(words.slice()); // Crea una copia e mescola // Calcola quanti "slot" di animazione ci sono basati sulla durata totale e l'intervallo medio const averageInterval = (minBlinkInterval + maxBlinkInterval) / 2; const numAnimationSteps = Math.max(words.length, Math.ceil(totalDuration / (averageInterval + blinkDuration))); let currentTime = 0; // Aggiunge animazioni alla timeline per ogni parola shuffledWords.forEach((word, index) => { // Calcola un ritardo casuale per il prossimo lampeggio const randomDelay = gsap.utils.random(minBlinkInterval, maxBlinkInterval); const startTime = currentTime; // Aggiunge l'animazione di lampeggio alla timeline tl.to(word, { opacity: 1, // Rende visibile duration: blinkDuration / 2 // Metà durata per apparire }, startTime) // Posiziona l'inizio dell'animazione .to(word, { opacity: minOpacity, // Rende quasi invisibile duration: blinkDuration / 2, // Metà durata per scomparire delay: blinkDuration / 2 // Aspetta che sia apparsa }, startTime) // Posiziona anche questa parte allo stesso start time .to(word, { // Assicura che alla fine sia visibile opacity: 1, duration: 0.01 // Transizione rapidissima alla fine del suo "slot" }, currentTime + averageInterval); // Posiziona leggermente dopo l'inizio // Aggiorna il tempo corrente per il prossimo lampeggio currentTime += randomDelay; // Se abbiamo esaurito le parole ma non la durata, riutilizziamo le parole if (index === shuffledWords.length - 1 && currentTime < totalDuration) { shuffledWords = gsap.utils.shuffle(words.slice()); // Rimescola // Potresti voler aggiungere qui logica per evitare ripetizioni immediate // o semplicemente lasciare che il ciclo continui con le parole rimescolate // per semplicità, lasciamo che il forEach finisca e la timeline si completi // con la durata implicita data dai posizionamenti. } }); // Assicura che tutte le parole siano visibili alla fine dell'intera animazione // Aggiungiamo un'animazione alla fine della timeline calcolata tl.to(words, { opacity: 1, stagger: 0.05, // Applica un piccolo ritardo tra ogni parola alla fine duration: 0.3, ease: "power1.inOut" }, ">"); // ">" posiziona questa animazione alla fine della timeline esistente // Avvia la timeline tl.play(); } // Usa Intersection Observer per attivare l'animazione quando l'elemento entra nel viewport const observer = new IntersectionObserver((entries, observerInstance) => { entries.forEach(entry => { if (entry.isIntersecting) { // L'elemento è entrato nel viewport createAnimation(entry.target); // Smetti di osservare questo elemento una volta attivata l'animazione observerInstance.unobserve(entry.target); } }); }, { threshold: 0.1 // Attiva quando almeno il 10% dell'elemento è visibile }); // Inizia ad osservare l'elemento target observer.observe(target); }); });
H
alan
zirpoli
d
indipendent desinger
and art director.
I did works for brands like Bulgari, ENI, Ford, Assicurazioni Generali, BioTech Animated, Shapr 3D, Tonki, FMI, Deghi, MIR, Ibiza Token and more.
Collaborating with agencies and studios like Magilla, Ginko, Azimuth Film Company, Gravity, Asse Communication.
Collaborating with agencies and studios like Magilla, Ginko, Azimuth Film Company, Gravity, Asse Communication.
based in roma
2025
H
alan
zirpoli
d
indipendent desinger
and art director.
I've worked with Bulgari, Ford, Tonki, Magilla
based in roma
2025