阅读 1691

React Hooks自定义一个图片懒加载hooks

图片懒加载也是一个老生常谈的话题了,假如一个网站的图片很多,如果不对图片做懒加载处理,那么一打开这个页面,所有的图片都会向服务器发起资源请求,势必会出现资源浪费的情况(如果用户打开了这个网站,并没有往下面下方浏览,只是停留在了当前的这个位置,然后退出了网页,那么那些不需要展示的图片也已经加载完成了,这就造成了资源浪费);对于一些个人网站来说,图片懒加载更是必要的。

图片懒加载的定义

展示那些需要展示给用户的图片,话句话说,就是只有用户浏览到的地方,才需要加载出图片。

图片懒加载的实现

1.不直接给img标签的src属性赋值,可以先给其data-src属性值赋值图片的地址
2.当图片到顶部的距离大于可视区域和滚动区域之和的时候,再将data-src的值赋值给src

普通实现,使用getBoundingClientRect

getBoundingClientRect 返回元素的大小及其相对于视口的位置,是一个集合;集合中有top, right, bottom, left等属性。

top:元素上边到视窗上边的距离;
right:元素右边到视窗左边的距离;
bottom:元素下边到视窗上边的距离;
left:元素左边到视窗左边的距离;

先实现一个throttle节流的自定义hooks,scroll事件一般都要配合其使用


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
复制代码

实现图片懒加载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
复制代码

在组件里使用:

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 Test
复制代码

之所以使用savedCallback.current,其实道理和useEffect中使用setInterval一样,如果熟悉hooks的同学都知道这个坑,是因为闭包的问题,导致scroll事件的回调函数中一直拿到的都是闭包的scrollCount,一直是0;

使用IntersectionObserver API 自定义hooks

主要使用的是 IntersectionObserver 这个Web API,没有了解过的可以移步链接稍微看一看,上代码:

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

const useIntersectionObserver = (domList, deps = [0]) => {
    // 接收两个参数,dom元素的class和指定交叉比例(threshold)的依赖项

    const ioRef = useRef()

    useEffect(() => {

        ioRef.current = new IntersectionObserver((entries) => { // 观察者
            entries.forEach((item) => { // entries 是被监听的dom集合,是一个数组
                if (item.intersectionRatio <= 0) return // intersectionRatio 是可见度 如果当前元素不可见就结束该函数。
                const { target } = item
                target.src = target.dataset.src // 将 h5 自定义属性赋值给 src (进入可见区则加载图片)
            })
        }, {
            threshold: deps // 用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是[0]。
        });

        domList.forEach(item => ioRef.current.observe(item)); // observe 开始观察,接受一个DOM节点对象

        return () => {
            ioRef.current.disconnect(); // 关闭观察器
        }
    }, [])

}

export default useIntersectionObserver
复制代码

在组件里使用:

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 Test
复制代码
文章分类
前端
文章标签