C'est pas un secret que j'utilise de multiples effets déclenchés par défilement de la page.
Par exemple, le défilement infini qui sur mes pages de liste d'article ou brève et qui, par ailleurs, est l'essence même du réseau social moderne et son addictivité (je pense c'est un vrai mot?).
Du coup normalement on place un bon vieil ECOUTEUR D'ev3NEMENT sur l'objet qui est sujet du défilement et qui est générallement window ou document (mais pas nécessairement):
window.addEventListener('scroll', app.revealScrollCallback)
Cet événement se produit à chaque défilement, même tout petit. Autant dire que ça mitraille de l'événement et qu'il n'y en a pas toujours besoin.
Bien entendu on peut retirer l'ECOUTEUR D'ev3NEMENT une fois qu'on en a plus besoin mais cette pratique est rare.

Quelqu'un un moment a dû proposer un plan appelé Intersection Observer, qui est un peu bizarre à comprendre et alien à voir en JavaScript moderne parce qu'il faut instancier un objet — avec le mot clé new et tout.
L'idée étant d'enregistrer une fonction à appeler lorsque un ou plusieurs éléments du DOM entre en intersection avec un conteneur à spécifier, lequel est supposé avoir une partie "en vue" et une partie cachée qui peut être révelée par défilement.
Par défaut c'est la fenêtre / le document en cours.
J'essayerai d'expliquer tant que bien que mal ce que cette histoire d'intersection signifie (j'avais rien compris perso).
Bref, comme j'avais dit, faut instancier un bidule avec une fonction constructeur qui prend en paramètre la fonction callback appelée en cas d'intersection et un objet avec des options pour l'observateur d'intersection.
Cet objet est totalement optionel, les valeurs par défaut fonctionnent assez bien pour tous les cas basiques.
const interObserver = new IntersectionObserver((entries, observer) => {
for (const en of entries) {
if (en.isIntersecting === true) {
// FAIRE UN TRUC ICI
// l'élément en.target est en intersection
// avec ce qui est visible du conteneur.
}
}
},
{
root: document, // C'est la valeur par défaut
threshold: 0.5,
rootMargin: "0px" // Aussi la valeur par défaut
})
interObserver.observe(document.querySelector("#monSuperTitre"));
La fonction ECOUTEUR embarque deux arguments:
- Une liste des éléments qui sont inscrits pour... Observation (on a appelé .observe dessus comme en bas de l'extrait ci-dessus) — Ceux qui ont le booléen isIntersecting à true sont visibles à l'écran d'une proportion qui est d'au moins le pourcentage donné en threshold dans les options (j'en reparle plus loin);
- L'objet observateur à l'origine du callback — Pratique pour utiliser unobserve dessus, par ex:
const interObserver = new IntersectionObserver((entries, observer) => {
for (const en of entries) {
if (en.isIntersecting === true) {
// FAIRE UN TRUC ICI
// ...un genre de transition définitive
// J'ai plus besoin de l'observateur sur cet élément:
observer.unobserve(en.target)
}
}
})
Une manière bien pratique d'arrêter les callbacks à répétition.
Il y a d'autres infos dans les entrées données à la fonction, je vous laisse console.log et explorer tout ça vous-mêmes. Je pense qu'en général c'est pas trop nécessaire.
Au niveau des options on a:
- root — L'élément qui contient les éléments à observer pour intersection — Peut être un élément HTML standard qui a des barres de défilement, la valeur par défaut fonctionne pour la plupart des autres cas de figure;
- threshold — Pourcentage (sous forme d'un flottant de 0 à 1.0) de l'objet observé qui doit être visible pour que la fonction callback soit appelé — 0 par défaut, ce qui signifie que le callback est appelé au premier pixel visible de l'élément observé;
- rootMargin — Une histoire de marges virtuelles sur l'élément conteneur, j'ai rien compris, le laisser à 0 (par défaut) c'est pas mal.
Pour rendre le truc un peu plus pimenté, threshold peut aussi être un tableau de flottants. Le callback est alors appelé à chaque dépassement de chacun de ces pourcentages (qui sont censés être dans l'ordre du coup? Je sais pas lul).
Fort de mes premières expériences sur Codepen, je me suis rendu compte qu'on pouvait remplacer pas mal d'événement de défilement par ces écouteurs d'intersection.
En fait je pensais qu'on pouvait tous les remplacer, et c'est plus ou moins vrai mais pas tout à fait.
Les écouteurs d'intersection (je l'abrévie EI parce que j'en ai trop marre) ça fonctionne pas vraiment si les éléments dont on écoute l'intersection avec le conteneur sont plus grands que le dit conteneur.
Du coup je ne peux pas avoir accès à l'info d'où j'en suis dans le défilement d'un article de blog avec un écouteur d'intersection.
Je pourrais éventuellement créer un truc qui ressemble en place un EI sur, par exemple, tous les titres de l'article (élements <h1>, <h2>, etc. Et ajouter mettre à jour la progression en fonction de la position relative du titre en question dans l'article, mais c'est pas super génial.
J'ai un genre d'exemple ci-dessous où je récupère la liste de TOUS les paragraphes de l'article, puis j'ajoute des écouteurs sur certains d'entre-eux à des intervalles plus ou moins réguliers de l'article.
C'est très bizarre. Comme j'ai un jeu de 6 couleurs différentes, il y a 6 vieux éléments p qui écouter leur intersection avec le défilement général du document. Je vous montre:
Pour ce qui est d'un exemple avec plusieurs seuils de transition, j'ai pensé aux images de l'article qui pourraient être révélées lors du défilement (opacité qui augmente progressivement).
J'avais été trop impressionné par certains frameworks hybrides JavaScript qui ont aussi une partie serveur (ou sont un générateur de site statique comme Gatsby) et ont du contenu qui se révèle au défilement, avec chargement d'image différé de sorte que seules les images à l'écran soient réellement téléchargées.
C'est certes avantageux pour les petits forfaits mobiles. A part ça je trouve ça moyennement utile au nvieau performances, c'est surtout l'effet WOW MODERNE de la page qui se révèle lors du défilement tel un diapositif de Powerpoint sur lequel on a mis une jolie transition de ce que laquelle qu'on est trop fier.
J'ai un projet de design (faut le dire vite) de nouveau blog qui traine depuis je sais pas combien d'années. Je l'ai repris y a pas longtemps pour ajouter un effet d'image qui se révèle sans qu'il y ait de chargement anticipé ni quoi que ce soit du genre.
Il y a aussi un vieux web component que j'ai écrit il y a longtemps et qui gère l'agrandissement de l'image si JavaScript est activé (sinon ça ouvre un nouvel onglet) mais c'est totalement sans rapport, le truc qui utilisent un EI c'est l'image qui apparaît sur défilement.
J'utilise 5 étapes pour le threshold mais ça fonctionne très bien avec 4 ou 3. Ou moins. En fait ce serait peut-être mieux d'utiliser jutse une valeur plutôt qu'un tableau mais j'avais envie de trouver au moins un exemple avec plusieurs thresholds.
D'après mes recherches, les autres gens ont du mal à trouver ce genre d'exemple tout du même.
Le code tient en assez peu de lignes:
const artImgs = document.querySelectorAll(".article-image")
for (const img of artImgs) {
img.classList.add("to-reveal")
}
const steps = 5
const incr = 1.0 / steps
const revThres = []
for (let i = 1; i <= steps; i++) {
revThres.push(incr * i)
}
const imgObserver = new IntersectionObserver((entries, observer) => {
for (const en of entries) {
if (en.isIntersecting) {
en.target.style.opacity = en.intersectionRatio
if (en.intersectionRatio === 1) {
observer.unobserve(en.target)
}
}
}
},
{ threshold: revThres })
artImgs.forEach(a => imgObserver.observe(a))
Où:
- Je sélectionne toutes mes images, elles ont toutes la classe CSS article-image dans mon cas donc c'est facile;
- Je leur ajoute la classe qui place leur opacité à 0.1s (+ une transition qui va bien) — Cette classe ne sera pas ajoutée si JavaScript est désactivé, auquel tout ira bien quand même avec les image visibles immédiatement;
- Je crée le tableau de threshold à partir de PUE GENIE mathématique, comme quand je distribue le PQ entre les 5 différentes toilettes de mon immense propriété;
- J'enregistre le callback qui applique simplement l'intersectionRatio en cours (de 0 à 1.0) comme propriété CSS opcacity;
- Si l'opacité est à 1, on arrête d'écouter sur cet élement, ça sert plus à rien. L'image reste visible pour toujours;
- J'enregistre l'événement sur toutes les images récupérées initialement.
Ce truc a l'air expérimental mais il est supporté par tous les navigateurs modernes. Je conseille d'essayer de l'utiliser en lieu et place d'un événement "scroll" tant que c'est possible, et c'est presque toujours possible.
Commentaires
Il faut JavaScript activé pour écrire des commentaires ici