IntersectionObserver (https://developer.mozilla.org/fr/docs/Web/API/Intersection_Observer_API) est une API javascript qui permet d'observer l'intersection entre une zone scrollable et des éléments qu'elle contient. Cela permet sur une page avec une barre de défilement de savoir lorsqu'un élément html devient visible pour l'utilisateur.
Cette API est très simple à utiliser, il suffit d'instancier un nouvel IntersectionObserver en lui passant une fonction de callback appelé lorsque l'intersection avec le éléments observés change:
var obs = new IntersectionObserver((elements) => {
console.log(elements);
}, {
root: null
rootMargin: '50px 0px', //Applique une marge de 50px verticalement
threshold: 0.01, //Seuil d'intersection des éléments (10%)
});
Paramètres du constructeur :
Exemple de pourcentages d'intersection d'éléments HTML avec le viewport
Une fois qu'on a instancié un observer on peut commencer à observer des éléments avec la méthode observe :
var imageTitre = document.getElementById("logo")
obs.observe(imageTitre); //Commence à observer les intersections avec l'élément
...
obs.unobserve(imageTitre); //Arrête d'observer les intersection avec cet élément
Il est bien plus optimal de réutiliser un IntersectionObserver pour observer plusieurs éléments plutôt que d'en instancier un pour chaque élément qu'on veut observer, et il faut bien penser à arrêter l'observation d'un élément dès qu'on en a plus besoin.
L'a fonction de callback est exécuté sur le thread principal avec une priorité basse. Si le navigateur a peu de ressources disponible, la fonction de callback peut donc être appelé bien après que le seuil d'intersection ai été franchis. L'IntersectionObserver n'est donc pas adapté pour gérer des animations ou autres utilisations nécessitant des temps de réponses précis.
En développement Web, on cherche souvent à optimiser un maximum le temps de chargement de nos pages, pour ça une bonne solution et de faire un lazy loading des images de la page.
L'IntersectionObserver est très adapté pour faire du lazy loading d'image sur une page web. C'est à dire ne pas charger toutes les images d'une page à son chargement, mais uniquement lorsqu'elles deviennent visibles.
Pour cela il suffit de stocker l'url de l'image dans un attribut data, et de ne l'affecter à l'attribut src uniquement quand l'image a une intersection avec le viewport
function loadImage(img) {
img.src = img.attributes["data-src"].value;
}
if ('IntersectionObserver' in window) {
let obs = new IntersectionObserver((entries) => {
entries.forEach(img => {
loadImage(img.target);
obs.unobserve(img.target);
})
}, {
rootMargin: "15px 0px",
threshold: 0.01
})
document.querySelectorAll(".lazy-image").forEach(img => obs.observe(img));
}
else {
document.querySelectorAll(".lazy-image").forEach(img => loadImage(img));
}
Et dans le HTML on a :
<img class="lazy-image" src="/images/default-image.png" data-src="/images/real-image.png" height="150px"/>
Ici le rootMargin à 15px 0px, avec un threshold à 10% fait que lorsque l'utilisateur scroll verticalement la page, l'image de 150px de hauteur ne sera chargée que quand le bord de l'image touche le viewport. On peut ajuster le rootMargin et threshold pour commencer ce chargement plus tôt ou plus tard
On a vu comment réaliser un lazy loading d'image en javascript pure, mais ce code ne marcherait pas directement avec un framework javascript comme reactjs.
Voici un exemple typescript d'une classe qui en plus d'instancier un IntersectionObserver va conserver un Map pour associer les éléments observés avec une fonction de callback spécifique
export class ElementObserver {
private static observedImages = new Map<Element, () => void>();
private static Observer = 'IntersectionObserver' in window ?
new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.intersectionRatio > 0) {
let elt = entry.target;
ElementObserver.observedImages.get(elt)();
ElementObserver.unobserve(elt);
}
});
}, {
rootMargin: '50px 0px',
threshold: 0.01
})
: null;
public static observe(elt: Element, callback: () => void) {
if (ElementObserver.Observer != null) {
ElementObserver.Observer.observe(elt);
ElementObserver.observedImages.set(elt, callback);
} else {
callback();
}
}
public static unobserve(elt: Element) {
if (ElementObserver.Observer != null) {
ElementObserver.observedImages.delete(elt);
ElementObserver.Observer.unobserve(elt);
}
}
}
En utilisant la classe ci-dessus, on peut facilement créer un composant react (en typescript) qui fait un lazy loading d'images :
export class LazyImage extends React.Component<{src:string, className?:string, style?:any}, {loaded:boolean}> {
imgRef: React.RefObject<HTMLImageElement>;
constructor(props) {
super(props);
this.state = {
loaded:false
};
this.imgRef = React.createRef<HTMLImageElement>();
}
componentDidMount(){
ElementObserver.observe(this.imgRef.current, this.onImgVisible.bind(this));
}
componentWillUnmount() {
if (this.imgRef.current)
ElementObserver.unobserve(this.imgRef.current);
}
onImgVisible() {
this.setState({loaded:true});
}
render() {
const defaultImage = "/images/default-image.png";
return (<img ref={this.imgRef} style={this.props.style} className={this.props.className} src={this.state.loaded ? this.props.src : defaultImage}/>);
}
}
ReactDOM.render(<LazyImage src="/images/logo.png" />, document.getElementById('root'));
La spec de l'API IntersectionObserver est encore en draft, mais la fonctionnalité est déjà supportée sur tous les navigateurs (Edge, Chrome, Safari, Firefox, Opera). Si notre chef nous demande de faire un site compatible avec IE, après 30 minutes à lui expliquer pourquoi on est pas content on peut utiliser les polyfills w3c pour IntersectionObserver
Commentaires :
Aucun commentaires pour le moment
Laissez un commentaire :