Effet text reveal

Pour notre tout premier article, nous allons nous intéresser à comment intégrer un effet text reveal qui est un incontournable qu'on retrouve assez souvent dans les sites web créatifs.

On ne le présente plus, l'effet text reveal est vraiment un incontournable quand on veut animer un site web, il est simple, efficace et n'est pas prise de tête. Nous allons voir comment l'intégrer avec quelques librairies forts utiles.

Les bases

Rien de bien compliqué, le plus important à comprendre c'est le trick pour cacher et ensuite pouvoir révéler le text, on va commencer par l'animation la plus simple pour ensuite voir certaines plus travaillées.

Dans un premier temps, il faut que l'élément contenant le texte soit dans un conteneur qui va servir pour le cacher.

        
<div class="container">
  <div class="text">
    Texte à cacher
  </div>
</div>
    

Overflow: clip

En CSS, la propriété overflow est utilisée pour contrôler la gestion du contenu qui dépasse la taille d'un conteneur. Elle détermine ce qui arrive au contenu lorsque celui-ci est trop grand pour s'insérer dans une boîte contenant des dimensions fixes (largeur et/ou hauteur).

La valeur par défaut est visible mais celle qui nous intéresse ici c'est la valeur clip qui va nous permettre de cacher le texte.

Au final cette propriété sera appliquée sur l'élément conteneur de l'élément texte.

        
.container {
  overflow: clip;
}
    

Transform: translateY

L'étape suivante consiste à décaler verticalement le texte qui sera grâce à la propriété overflow vue précédemment, caché. Pour cela on va utiliser la propriété transform avec la valeur translateY() on va appliquer un décalage de 100%, cela revient à décaler le texte de 100% de sa hauteur.

        
.text {
  transform: translateY(100%);
}
    

L'animation

Là encore rien de compliqué non plus, il suffit de mettre la valeur de translateY() à 0 pour voir le texte se décaler vers sa position initiale.

On serait donc tenté d'utiliser les @keyframes mais on va plutôt utiliser l'API Web Animation (WAAPI) en javascript du fait que ces animations sont plutôt faites pour être trigger à l'apparition de la zone du texte mais on va voir cela petit à petit.

NB: Je sais qu'il y a la propriété CSS animation-timeline mais vu que ce n'est pas encore compatible sur Firefox et Safari je ne vais pas m'y attarder, je ferai surement un update ou un autre article quand ce sera le cas.

Animate()

Pour utiliser la WAAPI plusieurs méthodes et la plus simple c'est la fonction animate() , elle accepte deux arguments : les keyframes qui vont être les propriétés CSS appliquées de manière séquentielle ainsi que les options qui comprennent les propriété de timeline à savoir duration, delay, fill, easing, etc ..

Ainsi notre code va se présenter de cette manière : el.animate(keyframes, options) .
Et avec notre exemple cela donne :

        
const text = document.querySelector('.text');

text.animate(
 [{transform: "translateY(0)"}],
 {
  duration: 300,
  easing: 'ease-out'
  fill: 'forwards',
 }
);
    

On aurait pu comme premier argument mettre [{transform: "translateY(100%)"}, {transform: "translateY(0)"}] mais ce n'est pas utile si on a déjà appliqué le translateY(100%) en CSS au début, le fill: "forwards" est important pour garder l'animation à son état final tout comment lorsqu'on utilise les @keyframes .

Donc juste avec ceci nous avons notre animation souhaitée, sauf que bien évidement cette dernière se lance tout de suite et ce que l'on veut c'est qu'elle soit trigger dès que l'élément soit au dessus de la ligne de flottaison et pour cela nous allons utiliser l'Intersection Observer API.

Intersection Observer API

L'intersection Observer API permet d'observer un élément lorsqu'il dépasse la ligne de flottaison donc lorsqu'il est visible et on peut même jouer sur le pourcentage du viewport pour trigger l'animation.

        
const text = document.querySelector('.text');
const io = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if(entry.isIntersecting) {
      entry.target.animate(
        [{transform: "translateY(0)"}],
        {
          duration: 300,
          easing: 'ease-out',
          fill: 'forwards',
        }
      );
      io.unobserve(entry.target);
    }
  })
},
{
  rootMargin: "0px 0px -40% 0px",
});

io.observe(text);
    

La fonction unobserve() permet d'arrêter d'observer l'élément déclenché car inutile et gain de performance surtout si on fait ça sur chaque mot de plusieurs paragraphes et le rootMargin qui nous permet de contrôler à partir de quel pourcentage du viewport l'élément sera trigger et il fonctionne comme le shorthand margin.

Avec un paragraphe

Jusqu'ici c'était plutôt simple mais c'est bien moins le cas pour un texte complet, si vous réfléchissez bien vous allez vite comprendre que si on applique ce qu'on a vu avant sur un texte de la manière simple, on aura tout le bloc de texte qui se déplace d'un coup et ce n'est absolument pas ce que l'on cherche ici mais plutôt que ça le fasse mot par mot, c'est pour cela qu'on va utiliser une libraire qui va bien nous aider et c'est SplitType.

SplitType

L'idée c'est de pourvoir animer mot par mot et pour cela chaque mot doit être dans un conteneur, c'est moche au niveau du DOM mais malheureusement il n'y a pas d'autre manière.

Pour faire cela, on peut compter sur SplitType justement, on peut soit l'utiliser via CDN ou l'installer avec npm (ou autre que vous préférez) et l'importer.

        
<script src="https://unpkg.com/split-type"></script>
    
        
import SplitType from 'split-type';
    

Ensuite on peut utiliser l'instance SplitType pour target l'élément du texte

        
new SplitType('.text');
    

Par défaut, cela aura pour effet de séparer le texte en lignes, mots et caractères en plus de leur attribuer une classe adéquate pour les cibler facilement

screen html
Résultat de SplitTypes par défaut

Dans notre exemple nous n'avons pas besoin de séparer les caractères, on va donc passer un argument {types} pour ne séparer que les lignes et les mots.

        
new SplitType('.text', {types: 'lines, words'});
    

Ce qui donne en combinant le tout

        
new SplitType('.text', {types: 'lines, words'});

const text = document.querySelector('.text');
const io = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if(entry.isIntersecting) {
      entry.target.querySelectorAll('.word').forEach((word) => {
        word.animate(
          [{transform: "translateY(0)"}],
          {
            duration: 300,
            easing: "ease-out",
            fill: 'forwards',
          }
        );
      });
      io.unobserve(entry.target);
    }
  })
}, 
{
  rootMargin: "0% 0% -50% 0%",
});

io.observe(text);
    

Il reste une dernière chose à ajouter car à ce stade tous les .word sont trigger en même temps mais nous voulons que cela se fasse en "stagger" donc de manière séquentielle.

Stagger

Pour cela on va juste jouer sur le delay avec lequel on va multiplier par index pour que les .words se décalent.

        
new SplitType('.text', {types: 'lines, words'});

const text = document.querySelector('.text');
const io = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if(entry.isIntersecting) {
      entry.target.querySelectorAll('.word').forEach((word, index) => {
        word.animate(
          [{transform: "translateY(0)"}],
          {
            duration: 300,
            delay: index * 20,
            easing: "ease-out",
            fill: 'forwards',
          }
        );
      });
      io.unobserve(entry.target);
    }
  })
}, 
{
  rootMargin: "0% 0% -50% 0%",
});

io.observe(text);
    

Il est important de noter que dans ce code, on observe l'élément .text et on attend qu'il apparait pour seulement appliquer l'animation sur chaque mot.

S'il y a plusieurs élément .text ne pas oublier de boucler pour observe() chaque élément.

Autres librairies

GSAP

Pour les utilisateurs de GSAP c'est plus simple et moins verbeux, on utilisera le plugin ScrollTrigger pour lancer l'animation dès que les éléments sont visibles.

        
new SplitType('.text', {types: 'lines, words'});

const text = document.querySelector('.text');

gsap.to(text.querySelectorAll('.word'), {
  scrollTrigger: {
    trigger: text,
    start: 'top 50%',
  },
  y: 0,
  stagger: 0.025,
});
    

Motion

J'ai une large préférence pour Motion car contrairement à GSAP, il utilise la WAAPI donc un gain de performance (cet article détaille bien la différence entre les deux) et en plus on peut jouer avec les spring animations mais ce n'est pas le sujet (sauf si vous voulez ajouter plus d'effet bien sûr).

        
import { animate, inView, stagger } from "https://cdn.jsdelivr.net/npm/motion@11.11.13/+esm";

new SplitType(".text", { types: "lines, words" });

inView(".text", (el) => {
    animate(
      el.target.querySelectorAll(".word"),
      {
        y: "0"
      },
      { duration: 0.7, delay: stagger(0.025), easing: "ease-out" }
    );
  },
  { margin: "0% 0% 50% 0%" }
);
    

Conclusion

Voilà pour ce premier article, j'espère avoir été assez clair pour cet effet qu'on trouve un peu partout, à l'avenir je mettrai certainement des exemples bien travaillé dans des pages à part et aussi des cas d'études sur des sites web existant.