Créer un texte qui se révèle mot par mot au scroll en React
Pour révéler un texte mot par mot au scroll en React, on découpe la chaîne en mots, on mappe chacun sur un motion.span, et on pilote son opacité avec useTransform sur la valeur de progression de useScroll. Chaque mot reçoit un intervalle [start, end] proportionnel pour que la révélation s'enchaîne de gauche à droite au fur et à mesure du scroll.
- Stack : React 18 + Framer Motion 11, ~135 lignes, zéro dépendance supplémentaire au-delà de framer-motion.
- API principale : useScroll (target + offset), useTransform, motion.span avec willChange:'opacity'.
- La section nécessite au moins 80vh d'espace de scroll pour se dérouler correctement ; idéale avec des paragraphes de 20 à 40 mots.
- Accessible : le texte complet du paragraphe est toujours présent dans le DOM ; l'opacité est purement visuelle.
- Responsive par construction, la taille de police utilise clamp() et la mise en page se redimensionne naturellement sur mobile.
About Scroll Reveal est une section React qui anime un paragraphe de mission ou de manifeste mot par mot au fil du scroll. Chaque mot passe d'une opacité quasi nulle à pleine opacité dans l'ordre, transformant un bloc de texte en une déclaration qui se mérite. Le pattern est emprunté aux sites de marques haut de gamme comme Dior.com, rendu ici en un seul composant copy-paste.
Anatomie
La section est organisée en trois blocs verticaux dans un conteneur centré : un petit badge pill avec un label en majuscules, un h2 qui monte une fois à l'entrée, et un grand paragraphe où chaque mot est enveloppé dans son propre motion.span. Le paragraphe utilise display:flex + flex-wrap pour garder les spans de mots inline avec des retours à la ligne naturels. La ref du conteneur est attachée au wrapper du paragraphe de mots pour que l'offset scroll suive ce nœud DOM précis.
Comment ça marche
L'animation est entièrement pilotée par le hook useScroll de Framer Motion, qui retourne une valeur scrollYProgress de 0 à 1 pendant que l'élément cible traverse le viewport. L'offset `['start 0.8', 'end 0.3']` signifie que la progression commence quand le haut de la section atteint 80% vers le bas de l'écran et se termine quand le bas atteint 30%. Les mots sont découpés avec text.split(' '), et chacun reçoit un intervalle proportionnel : le mot i obtient [i / totalWords, (i+1) / totalWords]. useTransform mappe alors cet intervalle sur une opacité de [0.15, 1], les mots s'allument donc en séquence plutôt que tous en même temps.
Comment le coder en React
Mettre en place le contexte scroll avec une ref de conteneur
Crée une ref et passe-la comme target à useScroll. Règle l'offset pour que l'animation s'étende sur une fenêtre confortable pendant que la section entre en vue. Les valeurs d'offset suivent le format [pointDeDépart, pointDeFin] où '0.8' signifie 80% vers le bas du viewport.
const containerRef = useRef<HTMLDivElement>(null); const { scrollYProgress } = useScroll({ target: containerRef, offset: ["start 0.8", "end 0.3"], });Découper le paragraphe en mots et assigner les intervalles
Découpe la chaîne de texte par les espaces pour obtenir un tableau de mots. Pour chaque mot à l'index i dans un tableau de totalWords éléments, calcule start = i / totalWords et end = (i + 1) / totalWords. Ces deux nombres deviennent l'intervalle d'entrée pour useTransform.
const words = text.split(" "); const totalWords = words.length; // inside the map: const start = i / totalWords; const end = (i + 1) / totalWords;Animer l'opacité de chaque mot avec useTransform
Dans un sous-composant RevealWord, appelle useTransform pour mapper l'intervalle [start, end] de scrollYProgress sur une opacité de [0.15, 1]. Applique la valeur motion résultante à un motion.span. Ajoute willChange:'opacity' pour indiquer au navigateur de passer sur le GPU.
function RevealWord({ word, range, progress }) { const opacity = useTransform(progress, range, [0.15, 1]); return ( <motion.span style={{ opacity, willChange: "opacity" }}> {word} </motion.span> ); }Envelopper les mots dans un conteneur flex
Rends les mots dans une balise p stylée avec display:flex et flex-wrap:wrap. Chaque motion.span a besoin d'un marginRight pour ajouter l'espacement entre les mots, car les spans inline-block n'héritent pas des espaces normaux. Utilise une grande taille de police avec clamp() pour que le texte se lise comme une déclaration, pas comme du corps de texte.
<p style={{ display: "flex", flexWrap: "wrap", fontSize: "clamp(1.5rem, 3.5vw, 2.75rem)" }}> {words.map((word, i) => ( <RevealWord key={i} word={word} range={[i / total, (i+1) / total]} progress={scrollYProgress} /> ))} </p>
Quand l'utiliser
Ce pattern est pertinent quand tu as une déclaration de mission ou un manifeste de 20 à 40 mots qui mérite plus qu'un simple bloc de texte statique. Il fonctionne bien sur les pages d'agence, de portfolio et de landing de marque, placé tôt à mi-page. À éviter sur les dashboards denses, les pages produit e-commerce, ou tout contexte où les utilisateurs cherchent une réponse précise plutôt que de lire. La section a aussi besoin d'une hauteur verticale suffisante pour que l'animation scroll ait de l'espace ; sur des pages très courtes l'effet se déclenche trop vite.
Utilisé par
- Dior, A popularisé la révélation mot par mot sur ses pages de storytelling de marque comme signature éditoriale.
- Stripe, Utilise des animations de texte pilotées au scroll sur ses pages campagne et à propos pour rythmer la lecture des récits produit.
- Lusion, Site de studio portfolio utilisant la révélation de texte progressive comme dispositif narratif principal tout au long de la page.
- Awwwards, Ce pattern apparaît régulièrement dans les sites d'agence primés répertoriés sur la plateforme, ce qui en fait un geste UI premium reconnu.
FAQ
Pourquoi l'animation semble trop rapide ou trop lente ?
La vitesse est contrôlée par l'option offset de useScroll. Resserre l'intervalle (ex. `['start 0.6', 'end 0.5']`) pour accélérer, ou élargis-le (ex. `['start 0.9', 'end 0.1']`) pour ralentir. Tu peux aussi fixer un minHeight sur la section pour forcer une zone de scroll plus haute.
Peut-on partir d'une transparence totale (opacity 0) plutôt que 0.15 ?
Techniquement oui, modifie le tableau de sortie dans useTransform en [0, 1]. En pratique, partir de 0.15 garde la structure du texte visible pour que les utilisateurs perçoivent qu'il y a du contenu à révéler. Un zéro complet fait paraître le paragraphe vide avant de scroller, ce qui peut sembler cassé.
Ça fonctionne sur mobile ?
L'animation scroll fonctionne sur les appareils tactiles, il n'y a pas de dépendance au pointeur. La section est marquée responsive:true et utilise clamp() pour la taille de police. Le principal point d'attention est que le minHeight de 80vh occupe la majeure partie de l'écran ; sur les pages très courtes cela peut sembler lourd.
Comment gérer les espaces de ponctuation dans le découpage des mots ?
text.split(' ') suffit pour une prose standard. Si ton texte contient plusieurs espaces ou des caractères blancs spéciaux, filtre les chaînes vides avec .filter(Boolean) après le découpage. La ponctuation attachée à un mot (comme une virgule) reste avec ce mot et s'anime ensemble, ce qui est le comportement souhaité.