sandbox

Typo Animation : Anim.js Split Effect

Année

2025

Cette animation découpe un texte en lettres, en mots ou en lignes, et les anime un par un à leur entrée dans le viewport. Une fois les attributs posés, vous choisissez le mode de découpe, le délai global, l'écart entre chaque élément, la durée et le déclenchement au scroll. Le résultat : une révélation en cascade fluide qui donne du rythme à la typographie.

Attributs

data-letter-animate="true" : active l'animation

data-animate-mode="letters", "words", "lines" : type de découpe (lettres, mots ou lignes)

data-delay="800" : délai global avant le démarrage (en ms)

data-offset="30" : décalage de déclenchement au scroll (en %)

data-step-delay="60" : délai entre chaque lettre, mot ou ligne (en ms)

data-anim-duration="800" : durée de l'animation (en ms)

Attributs d'exemple

data-letter-animate = "true" activates the animation data-animate-mode= "letters", "words", "lines" type of animation (by letters, words, or lines) data-delay ="800" global delay before the animation starts (in ms) data-offset= "30" offset for starting animation (in %) data-step-delay= "60" delay between each letter/word/line (in ms) data-anim-duration= "800" duration of animation (in ms)

Librairie Anime.js (CDN)

<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js"></script>

Script de l'animation

<script> document.addEventListener('DOMContentLoaded', function() { var animatedElements = document.querySelectorAll('[data-letter-animate="true"]'); animatedElements.forEach(function(element) { const mode = element.getAttribute('data-animate-mode') || 'letters'; const delayAttr = parseInt(element.getAttribute('data-delay')) || 0; const stepDelayAttr = parseInt(element.getAttribute('data-step-delay')) || 50; const animDurationAttr= parseInt(element.getAttribute('data-anim-duration'))|| 1000; const offsetAttr = parseFloat(element.getAttribute('data-offset')) || 0; // Préparation du texte if (mode === 'letters' || mode === 'words') { element.innerHTML = element.innerHTML .replace(/(^|<\/?[^>]+>|\s+)([^\s<]+)/g, '$1<span class="letter-word">$2</span>'); const wordSpans = element.querySelectorAll('.letter-word'); wordSpans.forEach(function(wordSpan) { if (mode === 'letters') { wordSpan.innerHTML = wordSpan.textContent .replace(/\S/g, "<span class='letter'>$&</span>"); } else { wordSpan.classList.add('word'); } }); } else { const lines = element.innerHTML.trim() .split('<br>'); element.innerHTML = lines .map(line => `<span class="line">${line}</span>`) .join(''); } // Détection au scroll const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const target = entry.target; let targets = target.querySelectorAll( mode === 'letters' ? '.letter' : mode === 'words' ? '.word' : '.line' ); anime({ targets: targets, translateY: [100, 0], opacity: [0, 1], easing: 'easeInOutQuad', duration: animDurationAttr, delay: (el, i) => delayAttr + stepDelayAttr * i, begin: () => target.style.opacity = '1' }); observer.unobserve(target); } }); }, { threshold: 0, rootMargin: `${-offsetAttr}% 0px ${-offsetAttr}% 0px` }); observer.observe(element); }); }); </script>

CSS de l'animation

<style> [data-letter-animate] { opacity: 0; } .letter, .word, .line { display: inline-block; transform: translateY(100px); opacity: 0; } .letter-word { white-space: nowrap; } .line { display: block; overflow: hidden; } </style>