Pictures of lazy loading is a cliche topic, if many images of a web site, if you don’t do lazy loading processing images, then open this page, all of the images will request to the server resources, waste of resources is bound to happen (if the user opened this website, and not to browse below below, Just stay in the current position, and then exit the page, then those don’t need to display the image has already loaded, which is a waste of resources); For some personal websites, lazy image loading is necessary.

Image lazy loading definition

Show the images that need to be shown to the user, in other words, where the user is browsing, the images need to be loaded.

Image lazy loading implementation

1. Instead of directly assigning a value to the SRC attribute of the img tag, you can assign the address 2 of the image to the data-src attribute of the img tag first. When the distance from the image to the top is greater than the sum of the visible and scrolling areas, the value of data-src is assigned to SRC

Normal implementation, using getBoundingClientRect

GetBoundingClientRect returns the size of elements and their positions relative to the viewport, which is a collection; We have top, right, bottom, left, and so on.

Top: The distance from the top of the element to the top of the window; Right: The distance from the right of the element to the left of the window; Bottom: The distance from the bottom of the element to the top of the window; Left: The distance from the left of the element to the left of the window;

You need to implement a custom hooks for throttle-throttling. Scroll events are usually used with them


import React, { useRef, useState, useEffect } from 'react'

const useThrottle = (fn, wait, deps = []) => {
    let prev = useRef(0)
    const [time, setTime] = useState(wait)

    useEffect(() => {
        let curr = Date.now()
        if (curr - prev.current > wait) {
            fn()
            prev.current = curr
        }
    }, deps)
}

export default useThrottle
Copy the code

Implement image hooks useImageLazy

import React, { useState, useEffect, useRef } from 'react'
import useThrottle from './useThrottle'

const useImageLazy = (domList) => {
    const [scrollCount, setScrollCount] = useState(0)

    const savedCallback = useRef()

    const getTop = () => {
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
        let clientHeight = document.documentElement.clientHeight || document.body.clientHeight
        let len = domList.length
        
        for (let i = 0; i < len; i++) {
            let { top } = domList[i].getBoundingClientRect()
            if (top < (scrollTop + clientHeight)) {
                domList[i].src = domList[i].dataset.src
                domList.splice(i, 1)
                i--;
                len--;
            }
        }
    }

    const scrollEvent = () => {
        setScrollCount(scrollCount + 1)
    }

    useEffect(() => {
        savedCallback.current = scrollEvent
    })

    useEffect(() => {
        let tick = () => { savedCallback.current() }
        document.addEventListener('scroll', tick)

        return () => {
            document.removeEventListener('scroll', tick)
        }
    }, [])

    useThrottle(() => {
        getTop()
    }, 200, [scrollCount])

}

export default useImageLazy
Copy the code

Used in components:

import React, { useRef } from 'react' import useImagelazy from '.. /common/use/useImagelazy' const imgList = Array.from({ length: 8 }, (item, index) => (item = `https://www.maojiemao.com/public/svg/gen${index + 5}.png`)) const style = { display: 'block', width: '300px', height: '300px', marginTop: '50px', } const Test = () => { const domRef = useRef([]) useImagelazy(domRef.current) return ( <> {imgList.map((item, index) => ( <img ref={el => domRef.current[index] = el} key={`lazy-${index}`} data-src={item} style={style} /> ))} </> )  } export default TestCopy the code

If you are familiar with the hooks, it is because of the closure problem. The callback that causes the Scroll event is always going to get the closure’s scrollCount, which is always 0;

Customize hooks using the IntersectionObserver API

IntersectionObserver, a Web API, is mainly used. If you haven’t learned anything about it, you can take a look at the following code:

Import React, {useRef, useEffect} from 'React' const useIntersectionObserver = (domList, deps = [0]) => Const ioRef = useRef() useEffect(() => {ioref.current = new IntersectionObserver((entries) => {// observer entries.forEach((item) => {// Entries is the dom collection monitored, If (item.IntersectionRatio <= 0) return // intersectionRatio is visibility then terminate the function if the element is not visible. Const {target} = item target.src = target.dataset. SRC // assign h5 custom attribute to SRC (load image)})}, {threshold: Deps // is used to specify the cross ratio, which determines when to fire the callback function, and is an array. The default is [0]. }); domList.forEach(item => ioRef.current.observe(item)); Return () => {ioref.current.disconnect (); // observe; return () => {ioref.current.disconnect (); }}, [])} export default useIntersectionObserverCopy the code

Used in components:

import React, { useRef } from 'react' import useIntersectionObserver from '.. /common/use/IntersectionObserver' const imgList = Array.from({ length: 8 }, (item, index) => (item = `https://www.maojiemao.com/public/svg/gen${index + 5}.png`)) const style = { display: 'block', width: '300px', height: '300px', } const Test = () => { const domRef = useRef([]) useIntersectionObserver(domRef.current, [1]) return ( <> {imgList.map((item, index) => ( <img key={`lazy-${index}`} className='avatar-img' data-src={item} style={style} ref={el => domRef.current[index] = el} /> ))} </> ) } export default TestCopy the code