Prochaine ville-étape
Étape 4 · 118 kmDépart demain 13 h
Un mot découpé en lettres qui montent de 20 px l'une après l'autre, 80 ms d'écart, puis remplacé par le mot suivant toutes les 2,5 s : la seule cascade qui travaille sur le texte lui-même — 2 Ko de JavaScript, vos mots écrits dans le HTML, lu comme un mot entier par les lecteurs d'écran, lettres posées d'emblée si l'utilisateur réduit les animations.
Mis à jour le
Cet effet est gratuit — la catégorie Transitions en contient 52 au total, dont 9 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →
Prochaine ville-étape
Étape 4 · 118 kmDépart demain 13 h
Cours suivant
10 h 15 – 12 h 15 · 1re année
Ouverture des portes 10 min avant240 places
LIBRAIRIE · LA GRANDE MARÉE
Thème du mois
Les choix de nos libraires sur la table d'entrée
Le cadre .md-char-stage (250 px de haut, fond #1a1a24) centre .md-char-cascade. Le script y écrit une lettre par span.md-char : 2rem, graisse 700, blanc, display: inline-block pour que la lettre puisse bouger seule. Chaque lettre part invisible et 20 px plus bas, et l'animation mdCharIn la remonte en 0,4 s ; forwards la laisse en place. Le décalage est écrit par le script : animation-delay vaut i × 0,08 s. Un mot de six lettres est entièrement écrit en 0,8 s.
Le script parcourt chaque .md-char-cascade de la page. Il lit ses mots dans les enfants du conteneur — un élément par mot, par exemple <span>VANNES</span><span>LORIENT</span> — et, si le conteneur est vide, prend la liste de démonstration (MOTION, DESIGN, EFFECT, SMOOTH). showWord() vide le conteneur, écrit le mot entier dans un span.md-char-sr invisible à l'œil, puis les lettres, chacune marquée aria-hidden="true" ; une espace devient une espace insécable pour garder sa largeur. Toutes les 2,5 s, le mot suivant prend la place. Un IntersectionObserver ne fait tourner ce cycle que lorsque le bloc est visible ; avec un seul mot, il n'y a pas de cycle, la cascade se joue une fois.
Le cycle automatique est la nature de l'effet : un seul mot à lire, qui change à intervalle régulier, comme un panneau. Les lettres séparées ne gênent pas la lecture assistée : mesuré dans Chromium 151 et Firefox 153, l'arbre d'accessibilité contient « MOTION » d'un seul tenant, pas six lettres. Le cadre a sa propre classe, préfixée comme le reste de l'effet : deux effets de la bibliothèque collés sur la même page ne se réécrivent pas.
prefers-reduced-motion: reduce, le CSS pose chaque lettre à sa place, sans montée (animation: none; opacity: 1; transform: none). Le mot continue de changer toutes les 2,5 s, sans mouvement. Mesuré dans Chromium 151, Firefox 153 et WebKit 26 : six lettres à opacité 1 à la première mesure.span masqué visuellement, et les lettres animées sont cachées aux aides techniques. Mesuré : l'arbre d'accessibilité donne « MOTION », puis « DESIGN » 2,5 s plus tard. Le conteneur n'a pas d'aria-live : un changement toutes les 2,5 s annoncé à voix haute serait épuisant. Si le mot porte une information importante, écrivez-la aussi ailleurs dans la page.clearInterval), ou limitez le nombre de tours ; sur un écran d'affichage public (porte de salle, vitrine), le cycle est attendu.#1a1a24, plus de 15:1 une fois en place ; pendant les 0,4 s de montée, l'opacité grandit de 0 à 1.CSS (@keyframes, transform) + 2 Ko de JavaScript (IntersectionObserver, setInterval), aucune dépendance. Mesuré dans Chromium 151, Firefox 153 et WebKit 26 : même cascade, même changement de mot à 2,5 s, même mot entier exposé aux lecteurs d'écran, même pose directe sous mouvement réduit.
Sans IntersectionObserver, le cycle tourne en permanence. Sans JavaScript, vos mots écrits dans le conteneur s'affichent tels quels, côte à côte et sans animation : gardez le premier mot seul dans le HTML si l'affichage sans script compte, et ajoutez les autres par le script.
Copiez les trois blocs dans votre page. Aucune dépendance.
<div class="md-char-stage">
<div class="md-char-cascade"></div>
</div>
.md-char-stage,
.md-char-stage *,
.md-char-stage *::before,
.md-char-stage *::after {
box-sizing: border-box
}
.md-char-stage {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 250px;
overflow: hidden;
position: relative;
background: rgb(26, 26, 36);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.md-char-cascade {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.md-char {
display: inline-block;
font-size: 2rem;
font-weight: 700;
color: #fff;
opacity: 0;
transform: translateY(20px);
animation: mdCharIn 0.4s forwards;
}
@keyframes mdCharIn {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Le mot entier, lu par les lecteurs d'écran, invisible à l'œil */
.md-char-sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
}
/* Mouvement réduit : les lettres sont posées d'emblée, le mot change sans mouvement */
@media (prefers-reduced-motion: reduce) {
.md-char {
animation: none;
opacity: 1;
transform: none;
}
}
(function() {
// Character Cascade : le mot monte lettre par lettre, puis cède la place au suivant toutes les 2,5 s
document.querySelectorAll('.md-char-cascade').forEach(function(container) {
// Les mots : un élément enfant par mot (<span>VANNES</span><span>LORIENT</span>),
// sinon la liste de démonstration
var words = Array.prototype.map.call(container.children, function(el) {
return el.textContent.trim();
}).filter(Boolean);
if (!words.length) words = ['MOTION', 'DESIGN', 'EFFECT', 'SMOOTH'];
var currentWord = 0;
var intervalId = null;
function showWord() {
// Animations mises en pause (bouton Pause de la page, outils d'accessibilité) : on garde le mot affiché
var first = container.querySelector('.md-char');
if (first && getComputedStyle(first).animationPlayState === 'paused') return;
container.innerHTML = '';
var word = words[currentWord];
// Le mot entier pour les lecteurs d'écran ; les lettres, masquées pour eux, sont pour l'œil
var label = document.createElement('span');
label.className = 'md-char-sr';
label.textContent = word;
container.appendChild(label);
word.split('').forEach(function(char, i) {
var span = document.createElement('span');
span.className = 'md-char';
span.setAttribute('aria-hidden', 'true');
span.textContent = char === ' ' ? '\u00a0' : char;
span.style.animationDelay = (i * 0.08) + 's';
container.appendChild(span);
});
currentWord = (currentWord + 1) % words.length;
}
showWord();
if (words.length < 2) return;
// Le cycle ne tourne que lorsque le bloc est visible
if ('IntersectionObserver' in window) {
var obs = new IntersectionObserver(function(entries) {
if (entries[0].isIntersecting) {
if (!intervalId) intervalId = setInterval(showWord, 2500);
} else {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
}
}, {
threshold: 0
});
obs.observe(container);
} else {
// Repli : cycle permanent
intervalId = setInterval(showWord, 2500);
}
});
})();
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
Vos mots (HTML — enfants de .md-char-cascade) |
aucun : MOTION, DESIGN, EFFECT, SMOOTH | Un élément par mot, dans l'ordre d'affichage : <span>DROIT</span><span>CHIMIE</span>. Un seul mot : la cascade se joue une fois, sans cycle. |
Écart entre lettres (JS — i * 0.08) |
80 ms | 40 ms pour un mot long (12 lettres en 0,84 s) ; 120 ms pour un effet machine à écrire. Gardez l'écriture complète sous la moitié du cycle. |
Rythme du cycle (JS — setInterval(showWord, 2500)) |
2,5 s par mot | 4 s pour un affichage à lire de loin ; en dessous de 1,5 s, le mot n'a pas le temps d'être lu après sa cascade. |
Montée et durée (CSS — translateY(20px), mdCharIn 0.4s) |
20 px, 0,4 s | translateY(0.6em) garde la même proportion à toutes les tailles de texte ; rotateX(90deg) à la place donne des lettres qui basculent. |
Typographie (CSS — .md-char) |
2rem, graisse 700, blanc | Toute police marche, chaque lettre étant une boîte inline-block. Les ligatures et l'approche entre paires de lettres sont perdues : préférez les capitales ou une police à chasse régulière pour les grands titres. |
Le mot. Le script écrit le mot entier dans un span.md-char-sr, masqué à l'œil mais lu par les aides techniques, et marque chaque lettre animée aria-hidden="true". Mesuré dans Chromium et Firefox : l'arbre d'accessibilité contient « MOTION » d'un seul tenant. Gardez la règle CSS .md-char-sr si vous changez le style : sans elle, le mot apparaîtrait deux fois à l'écran.
Dans showWord(), après currentWord = (currentWord + 1) % words.length;, arrêtez l'intervalle quand currentWord revient à 0 : if (currentWord === 0) { clearInterval(intervalId); obs.disconnect(); }. Le dernier mot reste affiché. Pour un bouton pause, appelez simplement clearInterval(intervalId).
Les espaces sont gardées (elles deviennent des espaces insécables), donc « BELLE ÎLE » s'écrit correctement. Mais une ligne de lettres en inline-block ne passe pas à la ligne entre les mots : au-delà de 15 à 20 caractères, réduisez la taille ou découpez la phrase en plusieurs conteneurs, un par ligne.