From 896a772e6245bb3cf68a18c94603ab4f83f5678f Mon Sep 17 00:00:00 2001 From: Chalarangelo Date: Sat, 16 Jul 2022 18:24:39 +0300 Subject: [PATCH] Add LazyLoadImage --- snippets/LazyLoadImage.md | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 snippets/LazyLoadImage.md diff --git a/snippets/LazyLoadImage.md b/snippets/LazyLoadImage.md new file mode 100644 index 000000000..ffe89721f --- /dev/null +++ b/snippets/LazyLoadImage.md @@ -0,0 +1,80 @@ +--- +title: Lazy-loading image +tags: components,effect,state +expertise: advanced +cover: blog_images/strawberries.jpg +author: chalarangelo +firstSeen: 2022-07-29T05:00:00-04:00 +--- + +Renders an image that supports lazy loading. + +- Use the `useState()` hook to create a stateful value that indicates if the image has been loaded. +- Use the `useEffect()` hook to check if the `HTMLImageElement.prototype` contains `'loading'`, effectively checking if lazy loading is supported natively. If not, create a new `IntersectionObserver` and use `IntersectionObserver.observer()` to observer the `` element. Use the `return` value of the hook to clean up when the component unmounts. +- Use the `useCallback()` hook to memoize a callback function for the `IntersectionObserver`. This callback will update the `isLoaded` state variable and use `IntersectionObserver.disconnect()` to disconnect the `IntersectionObserver` instance. +- Use the `useRef()` hook to create two refs. One will hold the `` element and the other the `IntersectionObserver` instance, if necessary. +- Finally, render the `` element with the given attributes. Apply `loading='lazy'` to make it load lazily, if necessary. Use `isLoaded` to determine the value of the `src` attribute. + +```jsx +const LazyLoadImage = ({ + alt, + src, + className, + loadInitially = false, + observerOptions = { root: null, rootMargin: '200px 0px' }, + ...props +}) => { + const observerRef = React.useRef(null); + const imgRef = React.useRef(null); + const [isLoaded, setIsLoaded] = React.useState(loadInitially); + + const observerCallback = React.useCallback( + entries => { + if (entries[0].isIntersecting) { + observerRef.current.disconnect(); + setIsLoaded(true); + } + }, + [observerRef] + ); + + React.useEffect(() => { + if (loadInitially) return; + + if ('loading' in HTMLImageElement.prototype) { + setIsLoaded(true); + return; + } + + observerRef.current = new IntersectionObserver( + observerCallback, + observerOptions + ); + observerRef.current.observe(imgRef.current); + return () => { + observerRef.current.disconnect(); + }; + }, []); + + return ( + {alt} + ); +}; +``` + +```jsx +ReactDOM.render( + , + document.getElementById('root') +); +```