Ne perdons pas de temps, voici l'effet que je cherche à reproduire.
J'ai trouvé des exemples similaires sur la toile, par exemple le premier sur cette page. Mais le côté infini du mouvement n'est pas très convaincant. On y voit un blanc avant de reprendre.
Je me suis donc mis en tête de reproduire cet effet par moi-même.
Si vous êtes impatient, vous trouverez les sources du résultat final sur ce repository.
Dans cet effet, nous avons un titre principal et plusieurs sous-titres.
Pour ne faire apparaitre qu'un seul sous-titre à la fois, je veux pouvoir les placer comme bon me semble dans un <div>
, y compris en dehors de l'espace visible de ce <div>
. Et bien sûr, si le sous-titre déborde, il doit être caché.
Je vais donc mettre en place la structure suivante :
<div class="title-box"> <!-- J'entoure avec cet autre div uniquement pour pouvoir le placer dans une page -->
<h1>Titre principal</h1>
<div> <!-- Ce div va définir l'espace visible, je l'appellerai parfois <div> encadrant -->
<div>Sous-titre 1</div>
<div>Sous-titre 2</div>
<div>Sous-titre 3</div>
</div>
</div>
En l'état, voici ce que ça donne et on est d'accord, c'est moche pour le moment :
Tout d'abord, on va resserrer un peu tout ça en enlevant les marges sur le titre :
h1 {
margin: 0;
}
L'étape suivante est importante. Elle va me donner le pouvoir de placer mes sous-tires comme bon me semble dans le <div>
qui les entoure. Et en fait, c'est assez simple, le <div>
en question va prendre une position: relative;
et chaque sous-titre va prendre une position position: absolute;
. Les sous-titres auront une position qui est absolue relativement au parent.
Le seul hic est qu'en spécifiant la position: relative;
, le <div>
ne prend plus tout seul toute la place dont il a besoin et donc, on va l'aider un peu en lui spécifiant une hauteur.
.title-box > div {
position: relative;
height: 1.5em;
}
.title-box > div > div {
position: absolute;
}
Et c'est encore plus moche qu'avant car tous les sous-titres sont les uns sur les autres. Mais c'est exactement le pouvoir que je voulais acquérir. C'est-à-dire que j'ai le pouvoir de rendre tout ça encore plus moche avec la propriété top
par exemple.
.title-box > div > div {
position: absolute;
top: -1.5em;
}
Et voilà, les sous-titres sont non seulement les uns sur les autres et en plus ils chevauchent le titre principal. La bonne nouvelle, c'est qu'ils ne sont plus dans la zone visible de notre <div>
encadrant. On peut donc les cacher avec un petit overflow: hidden;
.
.title-box > div {
position: relative;
height: 1.5em;
overflow: hidden;
}
Nous voilà avec des sous-titres bien présents dans le DOM, mais caché et prêt à se faire animer.
Une petite précision, vous aurez remarqué que j'utilise des em
comme unité pour définir la hauteur du <div>
et pour la position des sous-titres. Cette unité permet de s'adapter à la taille de la font utilisée pour les sous-titres. Par contre, pour que celà fonctionne, la taille de la font devra préciser sur le <div>
encadrant.
Le but du jeu, pour que le code ne devienne pas trop compliqué, est de définir une seule animation qui fonctionnera pour les 3 sous-titres. Et grâce à un délai au démarrage, on obtiendra une fluidité dans le mouvement. Voici à quoi peut ressembler l'animation CSS :
@keyframes subtitle-rolling {
0% {
top: -1.5em;
}
10% {
top: 0;
}
33% {
top: 0;
}
40% {
top: 1.5em;
}
100% {
top: 1.5em;
}
}
L'idée de l'animation est de partir depuis une position qui est au-dessus de la partie visible (top: -1.5em;
), de rapidement le faire défiler sur la surface visible (top: 0;
). Ensuite, on le laisse un peu sur la surface visible (le temps que des internautes puissent le lire) avant de faire le faire disparaitre mais en dessous de l'espace visible (top: 1.5em;
). Lorsque l'animation redémarrera, le sous-titre sera à nouveau caché instantanément au-dessus de la surface visible.
On applique cette animation à chaque sous-titre :
.title-box > div > div {
position: absolute;
top: -1.5em;
animation: subtitle-rolling 6s linear infinite;
}
On est content, tous nos sous-titres s'animent, mais ils défilent en même temps et toujours les uns sur les autres. Il va falloir décaler le démarrage de l'animation pour chacun. Comme je suis fainéant, je vais essayer de rendre ça un peu générique.
Je vais utiliser les variables CSS pour m'aider, sur chaque sous-titre, je vais définir une variable CSS --subtitle-position
qui va être un simple index qui va de 0 à 2.
<div class="title-box"> <!-- J'entoure avec cet autre div uniquement pour pouvoir le placer dans une page -->
<h1>Titre principal</h1>
<div> <!-- Ce div va définir l'espace visible, je l'appellerai parfois <div> encadrant -->
<div style="--subtitle-position: 0;">Sous-titre 1</div>
<div style="--subtitle-position: 1;">Sous-titre 2</div>
<div style="--subtitle-position: 2;">Sous-titre 3</div>
</div>
</div>
Je veux faire démarrer chaque sous-titre à 2s d'écart. Pour pouvoir le faire, j'ai à ma disposition la propriété animation-delay
. Dans cette propriété, je vais réutiliser la variable que nous venons de définir en multipliant sa valeur par 2. Pour cette partie, le code est nettement plus parlant :
.title-box > div > div {
position: absolute;
top: -1.5em;
animation: subtitle-rolling 6s linear infinite;
animation-delay: calc(var(--subtitle-position) * 2s);
}
Ainsi, l'animation du premier sous-titre commencera immédiatement, le second commencera au bout de 1x2s et le dernier au bout de 2x2s. Et voici le résultat :
Voilà, j'espère que cet article vous aura été utile pour comprendre en détail ce qui se passe sur cet effet. J'ai essayé de reproduire le cheminement qui a été le mien tout au long. N'hésitez pas à me contacter, je prends toute forme de remarque.