图片懒加载

图片懒加载

之前学习vue2的时候,我了解到图片懒加载,只是当时没花时间去解决,仅仅是使用了vant2提供的懒加载插件,现在想尝试使用JS实现。

什么是懒加载?其实就是在页面渲染时,不是一次性加载全部的图片资源,针对图片元素只在视口范围内按需加载。可以节省加载时间和优化性能,这个功能在首屏加载中有很大的功效。下面给出对比图:

没使用懒加载的:

使用了懒加载的:

由此可见性能确实大幅度优化了

大致思路其实就是:在图片元素身上先不使用src属性,使用自定义属性data-src(名字可以随便取,格式为data-xxx)保存图片的路径,通过判断图片是否进入可见视口来讲data-src的值给src,使得图片可以显示。html代码如下:

    <img data-src="./img/h1.png" alt="">
    <img data-src="./img/h2.png" alt="">
    <img data-src="./img/h3.png" alt="">
    <img data-src="./img/h4.png" alt="">
    <img data-src="./img/h5.png" alt="">
    <img data-src="./img/h6.png" alt="">

先将一些常用的属性介绍一下:

属性 作用 说明
scrollLeft和scrollTop 被卷去的左侧和头部距离 配合页面滚动,这是可读写的
offsetLeft和offsetTop 获取元素距离自己定位父级元素的左上距离 获取元素时使用,这是只读的
clientWidth和clientHeight 获取元素的宽高 不包含border,margin和滚动条,获取元素大小,这是只读的
offsetWidth和offsetHeight 获取元素的宽高 包含border,padding和滚动条等,这是只读的

方法1:整体距离比较

总体思路是:通过比较页面卷去的高度、图片距离文档页面顶部的高度和浏览器视口的高度,如果卷去的高度与视口高度之和大于图片距离页面顶部的高度,就应该将图片加载出来,如下图:

  • offsetTop获取的是距离父元素的高度,此处父元素是body,所以可以使用offsetTop获取距离整个页面文档的高度
  • document.documentElement指的是html
        function lazyLoad() {
            const imgs = document.querySelectorAll('img')
            const clientHeight = document.documentElement.clientHeight
            const scrollTop = document.documentElement.scrollTop
            //n是第0张图片,为了减少for循环次数,对于前面的图片将不再进行判断操作
            let n=0
            for (let i = n; i < imgs.length; i++) {
                //不仅仅是高度的判断,同时自身属性如果已经有src属性,就不需要在进行赋值操作
                if(imgs[i].offsetTop<clientHeight+scrollTop && !imgs[i].getAttribute('src')){
                    //设置元素的属性:元素.setAttribute('属性名',属性值),如果之前没有这个属性,会新增;如果有,会将值修改
                    imgs[i].setAttribute('src',imgs[i].dataset.src)
                    n=i+1
                }
            }
        }

方法2:getBoundingClientRect()

该方法的返回值包含整个元素的最小矩形(包括 padding 和 border-width)。该对象使用 left、top、right、bottom、x、y、width 和 height 这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了 width 和 height 以外的属性是相对于视图窗口的左上角来计算的。

getboundingClientRect().top可以获取元素距离视口顶部的高度

这里我们去判断图片是否已经进入视口来决定是否给该图片的src赋值。

        function lazyLoad() {
            const imgs = document.querySelectorAll('img')
            let n = 0
            for (let i = n; i < imgs.length; i++) {
                const imgTop = imgs[i].getBoundingClientRect().top
                if (imgTop < window.innerHeight && !imgs[i].getAttribute('src')) {
                    imgs[i].setAttribute('src', imgs[i].dataset.src)
                    n = i + 1
                }
            }
        }

方法调用

由于图片是被用户采用滚动的形式来查看,因此我们可以监听window的scroll,只要页面发生滚动就要调用我们的lazyLoad方法

window.addEventListener('scroll', lazyLoad)

这样的话我们会发现在页面初始时,位于可视区域的图片没有显示,因为初始化时页面没有滚动,我们需要手动调用一次该方法

lazyLoad()

效果如下:

再次性能优化

尽管我们已经实现了要求,但是如果是事件监听的话,意味着每滚动一下都会调用lazyLoad函数,会造成不必要的资源浪费,所以使用节流函数来减少实践调用次数

首先得知道什么是节流:规定在一个单位时间内,只能触发一次函数。如果这个函数单位时间内触发多次函数,只有一次生效。

我使用了时间戳和定时器共同实现节流,代码如下:

        function debounse(fn,delay){
            let start=0
            let timer
            return function (){
                if(!start) start=new Date()
                let now=new Date()
                //上一次到现在还剩多少时间
                let time=delay-(now-start)
                //处理this的指向和传递参数
                let context=this
                let args=arguments
                //再次被调用时清空上一个定时器
                clearTimeout(timer)
                if(time<0){
                    fn.apply(context,args)
                    start=now
                }else{
                    timer=setTimeout(()=>{
                        fn.apply(context,args)
                        start=now
                    },time)
                }
            }
        }

最后调用的时候就直接调用debounce函数就可以了

完整代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>图片懒加载</title>
    <style>
        img {
            width: 510px;
            height: 600px;
            margin-top: 100px;
            display: block;
        }
    </style>
</head>

<body>
    <img data-src="./img/h1.png" alt="">
    <img data-src="./img/h2.png" alt="">
    <img data-src="./img/h3.png" alt="">
    <img data-src="./img/h4.png" alt="">
    <img data-src="./img/h5.png" alt="">
    <img data-src="./img/h6.png" alt="">

    <script>
        // 方法1
        // function lazyLoad() {
        //     const imgs = document.querySelectorAll('img')
        //     const clientHeight = document.documentElement.clientHeight
        //     const scrollTop = document.documentElement.scrollTop
        //     //n是第0张图片,为了减少for循环次数,对于前面的图片将不再进行判断操作
        //     let n=0
        //     for (let i = n; i < imgs.length; i++) {
        //         //不仅仅是高度的判断,同时自身属性如果已经有src属性,就不需要在进行赋值操作
        //         if(imgs[i].offsetTop<clientHeight+scrollTop && !imgs[i].getAttribute('src')){
        //             //设置元素的属性:元素.setAttribute('属性名',属性值),如果之前没有这个属性,会新增;如果有,会将值修改
        //             imgs[i].setAttribute('src',imgs[i].dataset.src)
        //             n=i+1
        //         }
        //     }
        // }
        // 方法2
        function lazyLoad() {
            const imgs = document.querySelectorAll('img')
            let n = 0
            for (let i = n; i < imgs.length; i++) {
                const imgTop = imgs[i].getBoundingClientRect().top
                if (imgTop < window.innerHeight && !imgs[i].getAttribute('src')) {
                    imgs[i].setAttribute('src', imgs[i].dataset.src)
                    n = i + 1
                }
            }
            // console.log('被调用了')
        }
        function debounse(fn,delay){
            let start=0
            let timer
            return function (){
                if(!start) start=new Date()
                let now=new Date()
                //上一次到现在还剩多少时间
                let time=delay-(now-start)
                //处理this的指向和传递参数
                let context=this
                let args=arguments
                //再次被调用时清空上一个定时器
                clearTimeout(timer)
                if(time<0){
                    fn.apply(context,args)
                    start=now
                }else{
                    timer=setTimeout(()=>{
                        fn.apply(context,args)
                        start=now
                    },time)
                }
            }
        }
        lazyLoad()
        window.addEventListener('scroll', debounse(lazyLoad,1000))
    </script>
</body>

</html>
posted @ 2023-03-22 20:09  超重了  阅读(63)  评论(0编辑  收藏  举报