Implémentation du défilement infini et du chargement paresseux des images dans React
HTML
Intersection Observer
pour implémenter le défilement infini et le chargement paresseux d'images dans un composant fonctionnel React. Dans le processus, nous apprendrons comment utiliser certains crochets de React et comment créer des crochets personnalisés.
Si vous avez cherché une alternative à la pagination, infinite scroll est une bonne considération. Dans cet article, nous allons explorer certains cas d'utilisation de l'API Intersection Observer dans le contexte d'un composant fonctionnel React. Le lecteur doit posséder une connaissance pratique des composants fonctionnels React . Une certaine familiarité avec les crochets React sera bénéfique mais pas obligatoire, car nous allons en examiner quelques-uns.
Notre objectif est qu'à la fin de cet article, nous aurons implémenté un défilement infini et chargement paresseux d'image à l'aide d'une API HTML native. Nous aurions également appris un peu plus de choses sur React Hooks. Avec cela, vous pouvez implémenter un défilement infini et un chargement paresseux d'images dans votre application React si nécessaire.
Commençons.
Création de cartes avec React et Leaflet
La saisie d'informations à partir d'un fichier CSV ou JSON n'est pas pas seulement compliqué, mais c'est aussi fastidieux. Représenter les mêmes données sous forme d'aide visuelle est plus simple. Shajia Abidi explique à quel point un dépliant est puissant et comment de nombreux types de cartes peuvent être créés. Lire l'article →
L'API Intersection Observer
Selon les documents MDN, «l'API Intersection Observer fournit un moyen d'observer de manière asynchrone les changements dans l'intersection d'un élément cible avec un élément ancêtre ou avec un élément ancêtre ou niveau de vue du document ".
Cette API nous permet d'implémenter des fonctionnalités intéressantes telles que le défilement infini et le chargement paresseux d'images. L'observateur d'intersection est créé en appelant son constructeur et en lui passant un rappel et un objet options. La fonction de rappel est invoquée chaque fois qu'un élément, appelé la cible
coupe la fenêtre d'affichage du périphérique ou un élément spécifié, appelé la racine
. Nous pouvons spécifier une racine personnalisée dans l'argument options ou utiliser la valeur par défaut.
let observer = new IntersectionObserver (callback, options);
L'API est simple à utiliser. Un exemple typique ressemble à ceci:
var intObserver = new IntersectionObserver (entrées => {
entries.forEach (entrée => {
console.log (entrée)
console.log (entry.isIntersecting) // renvoie true si la cible croise l'élément racine
})
},
{
// options par défaut
}
);
laissez target = document.querySelector ('# targetId');
intObserver.observe (cible); // start observation
entrées
est une liste d'objets IntersectionObserverEntry
. L'objet IntersectionObserverEntry
décrit un changement d'intersection pour un élément cible observé. Notez que le rappel ne doit pas gérer de tâche longue car il s'exécute sur le thread principal.
L'API Intersection Observer bénéficie actuellement d'une large prise en charge du navigateur, comme illustré sur caniuse .

Vous pouvez en savoir plus sur l'API dans les liens fournis dans la section des ressources.
Voyons maintenant comment utiliser cette API dans une véritable application React. La version finale de notre application sera une page d'images qui défile à l'infini et aura chaque image chargée paresseusement.
Faire des appels API avec le useEffect
Hook
Pour commencer, clonez le projet de démarrage de cette URL . Il a une configuration minimale et quelques styles définis. J'ai également ajouté un lien vers le CSS de Bootstrap
dans le fichier public / index.html
car j'utiliserai ses classes pour le style.
N'hésitez pas à créer un nouveau projet si vous le souhaitez. Assurez-vous que le gestionnaire de paquets yarn
est installé si vous souhaitez suivre le dépôt. Vous pouvez trouver les instructions d'installation de votre système d'exploitation spécifique ici .
Pour ce didacticiel, nous allons récupérer des images d'une API publique et les afficher sur la page. Nous utiliserons les API Lorem Picsum .
Pour ce didacticiel, nous utiliserons le point de terminaison, https://picsum.photos/v2/list?page=0&limit=10.
qui renvoie un tableau d'objets d'image. Pour obtenir les dix images suivantes, nous changeons la valeur de la page en 1, puis 2, etc.
Nous allons maintenant construire le composant App pièce par pièce.
Ouvrez src / App.js
et entrez le code suivant:
import React, {useEffect, useReducer} de 'react';
import './index.css';
fonction App () {
const imgReducer = (état, action) => {
commutateur (action.type) {
cas 'STACK_IMAGES':
return {... state, images: state.images.concat (action.images)}
cas 'FETCHING_IMAGES':
return {... state, fetching: action.fetching}
défaut:
état de retour;
}
}
const [imgData, imgDispatch] = useReducer (imgReducer, {images: []extraction: vrai})
// le bloc de code suivant va ici
}
Premièrement, nous définissons une fonction réductrice, imgReducer
. Ce réducteur gère deux actions.
- L'action
STACK_IMAGES
concatène le tableauimages
. FETCHING_IMAGES
actionne la valeur de la variablefetching
. entrevrai
etfaux
.
L'étape suivante consiste à câbler ce réducteur à un crochet useReducer
. Une fois cela fait, nous récupérons deux choses:
-
imgData
qui contient deux variables:images
est le tableau des objets d'image.la récupération
est un booléen qui nous indique si l'appel API est en cours ou non. imgDispatch
qui est une fonction de mise à jour de l'objet réducteur.
Vous pouvez en savoir plus sur le useReducer
hook dans la documentation React .
La partie suivante du code est l'endroit où nous appelons l'API. Collez le code suivant sous le bloc de code précédent dans App.js
.
// passez des appels API
useEffect (() => {
imgDispatch ({type: 'FETCHING_IMAGES', extraction: true})
récupérer ('https://picsum.photos/v2/list?page=0&limit=10')
.then (data => data.json ())
.then (images => {
imgDispatch ({type: 'STACK_IMAGES', images})
imgDispatch ({type: 'FETCHING_IMAGES', extraction: false})
})
.catch (e => {
// gère l'erreur
imgDispatch ({type: 'FETCHING_IMAGES', extraction: false})
retourner e
})
}, [ imgDispatch ])
// le bloc de code suivant va ici
Dans le crochet useEffect
nous appelons le point de terminaison API avec fetch
API. Nous mettons ensuite à jour le tableau d'images avec le résultat de l'appel d'API en envoyant l'action STACK_IMAGES
. Nous envoyons également l'action FETCHING_IMAGES
une fois l'appel API terminé.
Le bloc de code suivant définit la valeur de retour de la fonction. Entrez le code suivant après le crochet useEffect
.
return (
);
Pour afficher les images, nous mappons le tableau d'images dans l'objet imgData
.
Maintenant, lancez l'application et affichez la page dans le navigateur. Vous devriez voir les images bien affichées dans une grille réactive.
Le dernier bit est d'exporter le composant App.
exporter l'application par défaut;

La branche correspondante à ce stade est 01-make-api-calls .
Étendons maintenant cela en affichant plus d'images lorsque les pages défilent.
Implémentation d'Infinite Scroll
Notre objectif est de présenter plus d'images au fur et à mesure que la page défile. À partir de l'URL du point de terminaison de l'API, https://picsum.photos/v2/list?page=0&limit=10
nous savons que pour obtenir un nouvel ensemble de photos, nous avons seulement besoin d'augmenter la valeur de page
. Nous devons également le faire lorsque nous n'avons plus d'images à afficher. Pour notre objectif ici, nous saurons que nous n'avons plus d'images lorsque nous atteindrons le bas de la page. Il est temps de voir comment l'API Intersection Observer nous aide à atteindre cet objectif.
Ouvrez src / App.js
et créez un nouveau réducteur, pageReducer
ci-dessous imgReducer
.
// App.js
const imgReducer = (état, action) => {
...
}
const pageReducer = (état, action) => {
commutateur (action.type) {
cas 'ADVANCE_PAGE':
return {... state, page: state.page + 1}
défaut:
état de retour;
}
}
const [ pager, pagerDispatch ] = useReducer (pageReducer, {page: 0})
Nous définissons un seul type d'action. Chaque fois que l'action ADVANCE_PAGE
est déclenchée, la valeur de la page
est incrémentée de 1.
Mettez à jour l'URL dans la fonction fetch
pour accepter dynamiquement les numéros de page. comme indiqué ci-dessous.
fetch (`https://picsum.photos/v2/list?page=$ {pager.page} & limit = 10`)
Ajoutez pager.page
à la tableau de dépendances à côté de imgData
. Cela garantit que l'appel d'API s'exécutera chaque fois que pager.page
changera.
useEffect (() => {
...
}, [ imgDispatch, pager.page ])
Après le crochet useEffect
pour l'appel API, entrez le code ci-dessous. Mettez également à jour votre ligne d'importation.
// App.js
import React, {useEffect, useReducer, useCallback, useRef} de 'react';
useEffect (() => {
...
}, [ imgDispatch, pager.page ])
// implémente le défilement infini avec l'observateur d'intersection
laissez bottomBoundaryRef = useRef (null);
const scrollObserver = useCallback (
nœud => {
nouveau IntersectionObserver (entrées => {
entries.forEach (en => {
if (en.intersectionRatio> 0) {
pagerDispatch ({type: 'ADVANCE_PAGE'});
}
});
}) .observer (nœud);
},
[pagerDispatch]
);
useEffect (() => {
if (bottomBoundaryRef.current) {
scrollObserver (bottomBoundaryRef.current);
}
}, [scrollObserver, bottomBoundaryRef]);
Nous définissons une variable bottomBoundaryRef
et définissons sa valeur sur useRef (null)
. useRef
permet aux variables de conserver leurs valeurs à travers les rendus des composants, c'est-à-dire que la valeur actuelle de la variable persiste lorsque le composant contenant est rendu à nouveau. La seule façon de modifier sa valeur consiste à réaffecter la propriété .current
à cette variable.
Dans notre cas, bottomBoundaryRef.current
commence par une valeur de null
. Au fur et à mesure du déroulement du cycle de rendu de page, nous définissons sa propriété actuelle comme étant le nœud
Nous utilisons l'instruction d'affectation ref = {bottomBoundaryRef}
pour indiquer à React de définir bottomBoundaryRef.current
pour être le div où cette affectation est déclarée.
Ainsi,
bottomBoundaryRef.current = null
à la fin du cycle de rendu, devient:
bottomBoundaryRef.current =
Nous verrons où cette affectation est effectuée dans une minute.
Ensuite, nous définissons une fonction scrollObserver
dans laquelle placer l'observateur. Cette fonction accepte un nœud DOM
à observer. Le point principal à noter ici est que chaque fois que nous atteignons l'intersection sous observation, nous envoyons l'action ADVANCE_PAGE
. L'effet est d'incrémenter la valeur de pager.page
de 1. Une fois que cela se produit, le crochet useEffect
qui en fait une dépendance est réexécuté. Cette réexécution, à son tour, appelle l'appel de récupération avec le nouveau numéro de page.
Le cortège d'événements ressemble à ceci.
Frappez l'intersection sous observation → appelez
ADVANCE_PAGE
action → incrémentez la valeur depager.page
par 1 →useEffect
crochet pour les appels à récupérer →l'appel à chercher
est exécuté → les images renvoyées sont concaténées au tableauimages
.
Nous appelons scrollObserver
dans un crochet useEffect
afin que la fonction ne s'exécute que lorsque l'une des dépendances du crochet change. Si nous n'appelions pas la fonction dans un crochet useEffect
la fonction s'exécuterait à chaque rendu de page.
Rappelons que bottomBoundaryRef.current
fait référence à
. Nous vérifions que sa valeur n'est pas nulle avant de la passer à scrollObserver
. Sinon, le constructeur IntersectionObserver
retournerait une erreur.
Parce que nous avons utilisé scrollObserver
dans un crochet useEffect
nous devons l'envelopper dans un useCallback
hook pour empêcher la restitution des composants sans fin. Vous pouvez en savoir plus sur useCallback dans la documentation React.
Saisissez le code ci-dessous après la division
// App.js
...
{imgData.fetching && (
Obtention d'images
)}
Lorsque l'appel API démarre, nous définissons la récupération
sur true
et le texte Obtention d'images devient visible. Dès qu'il est terminé, nous définissons aller chercher
sur faux
et le texte est masqué. Nous pourrions également déclencher l'appel d'API avant d'atteindre la limite exactement en définissant un seuil différent
dans l'objet d'options du constructeur. La ligne rouge à la fin nous permet de voir exactement quand nous atteignons la limite de la page.
La branche correspondante à ce stade est 02-infinite-scroll .
Nous allons maintenant implémenter le chargement paresseux de l'image.
Implémentation du chargement paresseux des images
Si vous inspectez l'onglet réseau pendant que vous faites défiler vers le bas, vous verrez que dès que vous frappez la ligne rouge (la limite inférieure), l'appel d'API se produit et toutes les images commencent chargement même lorsque vous n'avez pas pu les visualiser. Il existe diverses raisons pour lesquelles ce comportement n'est peut-être pas souhaitable. Nous souhaitons peut-être enregistrer les appels réseau jusqu'à ce que l'utilisateur souhaite voir une image. Dans un tel cas, nous pourrions opter pour le chargement des images paresseusement, c'est-à-dire que nous ne chargerons pas une image avant qu'elle ne défile dans la vue.
Ouvrez src / App.js
. Juste en dessous des fonctions de défilement infinies, entrez le code suivant.
// App.js
// paresseux charge les images avec l'observateur d'intersection
// n'échange la source de l'image que si la nouvelle URL existe
const imagesRef = useRef (null);
const imgObserver = useCallback (node => {
const intObs = new IntersectionObserver (entrées => {
entries.forEach (en => {
if (en.intersectionRatio> 0) {
const currentImg = en.target;
const newImgSrc = currentImg.dataset.src;
// n'échange la source de l'image que si la nouvelle URL existe
if (! newImgSrc) {
console.error ('La source de l'image n'est pas valide');
} autre {
currentImg.src = newImgSrc;
}
intObs.unobserve (nœud); // détache l'observateur une fois terminé
}
});
})
intObs.observe (nœud);
}, []);
useEffect (() => {
imagesRef.current = document.querySelectorAll ('. card-img-top');
if (imagesRef.current) {
imagesRef.current.forEach (img => imgObserver (img));
}
}, [imgObserver, imagesRef, imgData.images]);
Comme pour scrollObserver
nous définissons une fonction, imgObserver
qui accepte un nœud à observer. Lorsque la page atteint une intersection, comme déterminé par en.intersectionRatio> 0
nous échangeons la source d'image sur l'élément. Notez que nous vérifions d'abord si la nouvelle source d'image existe avant d'effectuer le swap. Comme avec la fonction scrollObserver
nous encapsulons imgObserver dans un crochet useCallback
pour empêcher un nouveau rendu des composants.
Notez également que nous arrêtons l'observateur d'un img
élément une fois la substitution terminée. Nous le faisons avec la méthode unobserve
.
Dans le crochet useEffect
suivant, nous saisissons toutes les images avec une classe de .card-img-top
sur la page avec document.querySelectorAll
. Ensuite, nous itérons sur chaque image et définissons un observateur dessus.
Notez que nous avons ajouté imgData.images
comme dépendance du crochet useEffect
. Lorsque cela change, il déclenche le crochet useEffect
et à son tour imgObserver
est appelé avec chaque élément
.
Mettez à jour l'élément
comme indiqué ci-dessous.
Nous définissons une source par défaut pour chaque élément
et stockons l'image que nous voulons afficher dans la propriété data-src
. L'image par défaut a généralement une petite taille afin que nous téléchargions le moins possible. Lorsque l'élément
apparaît, la valeur de la propriété data-src
remplace l'image par défaut.
Dans l'image ci-dessous, nous voyons l'image de phare par défaut toujours affichée dans certains des espaces

La branche correspondante à ce stade est 03-lazy-loading .
Voyons maintenant comment nous pouvons résumer toutes ces fonctions afin qu'elles soient
Résumé de l'extraction, du défilement infini et du chargement paresseux dans des crochets personnalisés
Nous avons réussi à implémenter l'extraction, le défilement infini et le chargement paresseux des images. Nous pouvons avoir un autre composant dans notre application qui nécessite des fonctionnalités similaires. Dans ce cas, nous pourrions résumer et réutiliser ces fonctions. Tout ce que nous avons à faire est de les déplacer dans un fichier séparé et de les importer là où nous en avons besoin. Nous voulons les transformer en crochets personnalisés.
La documentation de React définit un crochet personnalisé comme une fonction JavaScript dont le nom commence par "utiliser"
et qui peut appeler d'autres crochets. Dans notre cas, nous voulons créer trois crochets, useFetch
useInfiniteScroll
useLazyLoading
.
Créez un fichier à l'intérieur du src /
dossier. Nommez-le customHooks.js
et collez le code ci-dessous à l'intérieur.
// customHooks.js
import {useEffect, useCallback, useRef} de 'react';
// effectuer des appels API et transmettre les données renvoyées via dispatch
export const useFetch = (données, répartition) => {
useEffect (() => {
dispatch ({type: 'FETCHING_IMAGES', extraction: true});
récupérer (`https://picsum.photos/v2/list?page=$ {data.page} & limit = 10`)
.then (data => data.json ())
.then (images => {
dépêche ({type: 'STACK_IMAGES', images});
dispatch ({type: 'FETCHING_IMAGES', extraction: false});
})
.catch (e => {
dispatch ({type: 'FETCHING_IMAGES', extraction: false});
return e;
})
}, [dispatch, data.page])
}
// bloc de code suivant ici
Le crochet useFetch
accepte une fonction de répartition et un objet de données. La fonction de répartition transmet les données de l'appel API au composant App
tandis que l'objet de données nous permet de mettre à jour l'URL du point de terminaison API.
// défilement infini avec observateur d'intersection
export const const useInfiniteScroll = (scrollRef, dispatch) => {
const scrollObserver = useCallback (
nœud => {
nouveau IntersectionObserver (entrées => {
entries.forEach (en => {
if (en.intersectionRatio> 0) {
expédition ({type: 'ADVANCE_PAGE'});
}
});
}) .observer (nœud);
},
[dispatch]
);
useEffect (() => {
if (scrollRef.current) {
scrollObserver (scrollRef.current);
}
}, [scrollObserver, scrollRef]);
}
// bloc de code suivant ici
Le crochet useInfiniteScroll
accepte une fonction scrollRef
et une fonction dispatch
. Le scrollRef
nous aide à configurer l'observateur, comme déjà expliqué dans la section où nous l'avons implémenté. La fonction de répartition permet de déclencher une action qui met à jour le numéro de page dans l'URL du point de terminaison de l'API.
// images de chargement paresseux avec observateur d'intersection
export const useLazyLoading = (imgSelector, items) => {
const imgObserver = useCallback (node => {
const intObs = new IntersectionObserver (entrées => {
entries.forEach (en => {
if (en.intersectionRatio> 0) {
const currentImg = en.target;
const newImgSrc = currentImg.dataset.src;
// n'échange la source de l'image que si la nouvelle URL existe
if (! newImgSrc) {
console.error ('La source de l'image n'est pas valide');
} autre {
currentImg.src = newImgSrc;
}
intObs.unobserve (nœud); // détache l'observateur une fois terminé
}
});
})
intObs.observe (nœud);
}, []);
const imagesRef = useRef (null);
useEffect (() => {
imagesRef.current = document.querySelectorAll (imgSelector);
if (imagesRef.current) {
imagesRef.current.forEach (img => imgObserver (img));
}
}, [imgObserver, imagesRef, imgSelector, items])
}
Le crochet useLazyLoading
reçoit un sélecteur et un tableau. Le sélecteur est utilisé pour rechercher les images. Tout changement dans le tableau déclenche le crochet useEffect
qui configure l'observateur sur chaque image.
Nous pouvons voir que ce sont les mêmes fonctions que nous avons dans src / App.js
que nous avons extrait dans un nouveau fichier. La bonne chose maintenant, c'est que nous pouvons passer des arguments de manière dynamique. Utilisons maintenant ces crochets personnalisés dans le composant App.
Ouvrez src / App.js
. Importez les crochets personnalisés et supprimez les fonctions que nous avons définies pour la récupération des données, le défilement infini et le chargement paresseux des images. Laissez les réducteurs et les sections où nous utilisons useReducer
. Collez le code ci-dessous.
// App.js
// importer des crochets personnalisés
import {useFetch, useInfiniteScroll, useLazyLoading} à partir de './customHooks'
const imgReducer = (état, action) => {...} // conserver cette
const pageReducer = (état, action) => {...} // conserver ceci
const [pager, pagerDispatch] = useReducer (pageReducer, {page: 0}) // conserve cette
const [imgData, imgDispatch] = useReducer (imgReducer, {images: []extraction: true}) // conserve cette
laissez bottomBoundaryRef = useRef (null);
useFetch (pager, imgDispatch);
useLazyLoading ('. card-img-top', imgData.images)
useInfiniteScroll (bottomBoundaryRef, pagerDispatch);
// conserve le bloc de retour
revenir (
...
)
Nous avons déjà parlé de bottomBoundaryRef
dans la section sur le défilement infini. Nous passons l'objet pager
et la fonction imgDispatch
à useFetch
. useLazyLoading
accepte le nom de classe .card-img-top
. Notez le .
inclus dans le nom de la classe. Ce faisant, nous n'avons pas besoin de le spécifier document.querySelectorAll
. useInfiniteScroll
accepte à la fois une fonction ref et la fonction dispatch pour incrémenter la valeur de page
.
La branche correspondante à ce stade est 04-custom-hooks .
Conclusion
Le HTML s'améliore en fournissant de belles API pour implémenter des fonctionnalités intéressantes. Dans ce post, nous avons vu à quel point il est facile d'utiliser l'observateur d'intersection dans un composant fonctionnel React. Dans le processus, nous avons appris à utiliser certains des crochets de React et à écrire nos propres crochets.
Ressources
- « Infinite Scroll + Image Lazy Loading », Orji Chidi Matthew, GitHub [19659128] " Boutons de défilement, de pagination ou de" chargement supplémentaire "infinis? Résultats d'utilisation dans le commerce électronique »Christian Holst, Smashing Magazine
- « Lorem Picsum »David Marby & Nijiko Yonskai
- « IntersectionObserver Coming Into View »Surma , Web Fundamentals
- Puis-je utiliser…
IntersectionObserver
- “ API Intersection Observer ,” Documents Web MDN
- “ Composants et accessoires ]"React
- "
useCallback
"React - "
useReducer
"React

Source link