实现图片懒加载的多种方式

前端 潘老师 2个月前 (12-18) 50 ℃ (0) 扫码查看

本文主要讲解关于实现图片懒加载的多种方式相关内容,让我们来一起学习下吧!

前言

图片懒加载是前端性能优化最常见的方式之一,它可以延迟加载网页中的图片。只有当图片出现在用户的可视区域后才加载,从而减少对服务器的请求。此外,图片懒加载还可以提高用户体验,因为页面可以更快地呈现给用户,而不需要等待所有图片加载完成。

下面我将介绍几种方法来实现图片的懒加载

图片懒加载的实现原理

当网页加载时,图片标签的src属性被设置为一个占位符通常为一张loading的图片,而真正的图片地址被保存在一个自定义的属性(例如data-src)中。

当用户滚动页面时,通过监听滚动事件,可以检测图片是否进入了可视区域。一旦图片进入可视区域,就将自定义属性中保存的图片地址赋值给src属性,从而触发图片的加载。这样可以实现在需要显示图片时再加载,而不是一次性加载所有图片。

这里比较关键的就是如何判断图片是否出现在可视区域了,我们可以通过传统的实现方式根据js中获取dom距离的一些api来实现,例如 offsetTop、offsetHeight、scrollTop、scrollHeight、innerHeight等。

还可以通过IntersectionObserver api来判断元素是否出现在可视区域,不熟悉这个api也没有关系。下面我将实现一个图片懒加载的功能带大家了解下

根据offsetTop – scrollTop <= innerHeight 判断

首先我们需要把所有的图片地址设置为一个loading图片,把图片的真实地址放到dom的自定义属性上,例如data-src='真实地址',然后使用window.addEventListener('scroll', lazyLoad)对页面滚动进行监听,每当页面滚动的时候都要实时的获取滚动条滚动的距离。浏览器的可视高度和元素距离顶部的距离是始终不变的。

当图片距离文档顶部的距离 – 当前文档滚动卷去的距离 <= 浏览器可视区域距离 就说明元素可见了,此时我们就把图片的地址从loading图片替换为真实的图片地址,完成懒加载。

window.innerHeight是浏览器的可视高度

document.documentElement.scrollTop是document滚动的距离

getElementById('img').offsetTop是img标签到document顶部的距离

下图是图片懒加载的演示,为了方便查看滚动的信息。我们只让一个img去实现图片懒加载的效果,其他的图片我们不做处理。

下面是具体的实现

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #app {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
        }

        .img {
            height: 300px;
            width: 300px;
            margin: 10px;
        }
    </style>
</head>

<body>
    <div id="app">
        <img class="img" src="./loading.gif"
            data-src="https://p26-passport.byteacctimg.com/img/user-avatar/fd965033fba9d2b1b67d0dd6d1c80ad3~150x150.awebp"
            alt="">
         <!--  ......此处省略一堆img  -->
    </div>
</body>
<script>
    function lazyLoad() {
        const imgs = document.querySelectorAll('.img')

        imgs.forEach(img => {
            if(img.offsetTop - document.documentElement.scrollTop <= window.innerHeight && !img.dataset.done){
                img.src = img.dataset.src
                // 表示该图片已经被替换了,避免向上滚动再回来反复触发
                img.dataset.done = true
            }
        })
    }

    window.addEventListener('DOMContentLoaded', lazyLoad);
    window.addEventListener('scroll', throttle(lazyLoad, 500));
    
    function throttle(fn, delay) {
        let timer
        return function () {
            if (timer) {
                return
            }

            fn.apply(this, arguments)

            timer = setTimeout(() => {
                timer = null
            }, delay);
        }
    }
</script>

</html>

采用getBoundingClientRect

原理都是一样的,只不过是判断元素出现在可视范围内的方式不同。

getBoundingClientRect用于获取其相对于视口的位置,这种方式更容易理解。

<!DOCTYPE html>
<html lang="en">
<body>
    <img id='img'/>
</body>
<script>
    const img = document.getElementById('img')
    // img这个元素距离document顶部的距离
    img.getBoundingClientRect().top
    // 约等于
    img.offsetTop - document.documentElement.scrollTop
    // 为什么是约等于 有什么不同
    // getBoundingClientRect().top 返回元素的上边界相对于视口的顶部的距离,以像素为单位。这个值是一个浮点数,可能会有小数部分。
    // offsetTop 返回元素的上边界相对于其 offsetParent 元素的顶部的距离,以像素为单位。
    // scrollTop 返回文档视口顶部相对于文档顶部的距离,以像素为单位。
    // 所以,虽然这两个表达式都提供了元素的上边界相对于视口顶部的距离,但是它们的计算方式和返回结果可能略有不同。
</script>

</html>

也就是说img.getBoundingClientRect().top距离小于window.innerHeight 就说明当前页面能看到图片,此时需要更换图片

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #app {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            gap: 10px;
        }

        .img {
            height: 300px;
            margin: 10px;
        }
    </style>
</head>

<body>
    <div id="app">
        <img class="img" src="./loading.gif"
            data-src="https://p26-passport.byteacctimg.com/img/user-avatar/fd965033fba9d2b1b67d0dd6d1c80ad3~150x150.awebp"
            alt="">
         <!--  ......此处省略一堆img  -->
    </div>
</body>
<script>
    function lazyLoad() {
        const lazyImages = document.querySelectorAll('.img');

        lazyImages.forEach(image => {
            const rect = image.getBoundingClientRect();
            if (rect.top <= window.innerHeight && !image.dataset.done) {
                image.src = image.dataset.src;
                image.dataset.done = true
            }
        });
    }

    window.addEventListener('DOMContentLoaded', lazyLoad);
    window.addEventListener('scroll', lazyLoad);

</script>

</html>

IntersectionObserver实现

IntersectionObserver的作用是监听目标元素与祖先元素是否有相交(默认是浏览器的视口),如果相交就触发事件。使用IntersectionObserver完成懒加载,优势在于我们不用再去计算元素距离判断图片是否已经出现在视口了,当图片出现在视口内时它会自动触发回调,完成我们回调内部的业务逻辑。

用这种方案,我们不需要监听滚动事件,也不需要进行距离判断,非常方便。

IntersectionObserver是个构造函数,需要我们通过new来创建实例。

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #app {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            gap: 10px;
        }

        .img {
            height: 300px;
            margin: 10px;
        }
    </style>
</head>

<body>
    <div id="app">
        <img class="img" src="./loading.gif"
            data-src="https://p26-passport.byteacctimg.com/img/user-avatar/fd965033fba9d2b1b67d0dd6d1c80ad3~150x150.awebp"
            alt="">
         <!--  ......此处省略一堆img  -->
    </div>
</body>
<script>
    const config = {
        root:null, //监听元素的祖先元素dom对象,其边界盒将被视作视口。默认为浏览器视口
        rootMargin:'0px 0px 0px 0px', // 距离视口多远触发回调函数
        threshold:'0' // 目标元素与设置root元素相交的比例,若指定值为 1.0,则意味着整个元素都在可见范围内时才触发回调
    }
    var observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            // isIntersecting为true说明出现在视口了
            if (entry.isIntersecting) {
                entry.target.src = entry.target.dataset.src
                // 更改过地址的img 就取消监听 优化性能
                observer.unobserve(entry.target)
            }
        })
    },config );

    const imgs = document.querySelectorAll('img')
    
    // 使用observer.observe监听所有的img
    imgs.forEach(img => {
        observer.observe(img)
    })

</script>

</html>

IntersectionObserver虽然用起来非常简单,因为是个较新的api。在使用之前需要考虑一下兼容性问题。

懒加载只是IntersectionObserver一个应用场景,只要是需要监听元素与其父元素或者视口之间有交叉状态的场景都可以考虑使用它。

结尾

IntersectionObserver 这个用来判断元素是否出现视口真的太方便了,总结懒加载的实现方法就是为了练习一下这个api,真是为了这点醋包的饺子。

以上就是关于实现图片懒加载的多种方式相关的全部内容,希望对你有帮助。欢迎持续关注潘子夜个人博客,学习愉快哦!


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/front/12617.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】