网站开发中,如何实现图片的懒加载

1,257 阅读2分钟

概念

图片懒加载实际上是一种网页优化技术。同普通静态资源一样,图片在被请求时,也会占用网络资源。若是一次性将整个页面的所有图片都加载完,这将大大增加页面的首屏加载时间(给人很卡很慢的现象)。为了解决这种问题,开发人员让图片仅在浏览器当前视窗内出现时才进行加载。而这种减少首屏图片请求数,且根据当前视口加载图片的的技术,就被称为“图片懒加载”。

上面说的可能有点绕,这里换句通俗的话:图片懒加载就是鼠标滑动到哪里,图片就加载到哪里。 当然,这话说得不是很严谨,但是胜在易理解。

实现思路

  1. 在 img 标签上自定义一个属性 data-src,用于存放真正需要显示的图片路径,而 img 的 src 属性上放一张大小为 1 * 1px 的图片路径,或者为空。
  2. 当页面滚动直至某个图片出现在可视区域时,就通过 js 获取该图片的 data-src 的值,然后赋给它的 src。

注意:自定义属性可以取任何名字,但要符合规范。

代码实现

方案一

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>图片懒加载</title>
        <style>
            img {
                width: 100%;
                height: 700px;
            }
        </style>
    </head>
    <body>
        <!-- 网图链接若是失效,可自行替换 -->
        <img src="https://pic.netbian.com/uploads/allimg/211120/005250-1637340770973a.jpg" alt="1" />
        <img src="https://pic.netbian.com/uploads/allimg/210920/165135-16321278956369.jpg" alt="2" />
        <img data-src="https://pic.netbian.com/uploads/allimg/211122/223735-1637591855c6b1.jpg" alt="3" />
        <img data-src="https://pic.netbian.com/uploads/allimg/211021/232902-16348301422b2a.jpg" alt="4" />
        <img data-src="https://pic.netbian.com/uploads/allimg/211109/221940-1636467580f68a.jpg" alt="5" />
        <!-- Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。 -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
        <script>
            const imgs = document.querySelectorAll('img');
            const imgList = Array.prototype.slice.call(imgs);
            const clientHeight = document.documentElement.clientHeight; // 视口高度
            const lazyLoad = () => {
                imgList.forEach(item => {
                    // 判断图片出现在了当前视口的条件:
                    // img元素的 CSSOM 对象到视口顶部的距离 < 视口高度 + 100px,加上 100px 是为了提前触发图片加载
                    if (item.getBoundingClientRect().top < clientHeight + 100) {
                        if ('src' in item.dataset) {
                            item.src = item.dataset.src;
                        }
                    }
                });
            };
            // 使用节流器(throttle),提高性能
            document.addEventListener('scroll', _.throttle(lazyLoad, 200));
        </script>
    </body>
</html>

相关知识点:

  1. Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。

  2. Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库(非常强大)。这里仅使用其封装的节流方法—— throttle,来提高性能。

若是对函数的节流与防抖比较感兴趣的话,不妨看看本人写的文章

方案二

const imgs = document.querySelectorAll('img');
const imgList = Array.prototype.slice.call(imgs);
const clientHeight = document.documentElement.clientHeight; // 视口高度

const lazyLoad = () => {
    // 滚动距离
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    imgList.forEach(item => {
        // 判断图片出现在了当前视口的条件:
        // 图片到浏览器顶部的距离 < 视口高度 + 滚动距离
        if (item.offsetTop < clientHeight + scrollTop && !item.src) {
            item.src = item.dataset.src;
        }
    });
};

// 使用节流器(throttle),提升性能
document.addEventListener('scroll', _.throttle(lazyLoad, 200));

同方案一相比,方案二代码主要是对 lazyLoad 函数进行了一些更改,其它基本不变。在这个函数中不在使用 getBoundingClientRect() 方法,而是通过 HTMLElement.offsetTopdocument.documentElement.scrollTop 来实现图片的懒加载。

方案三

Intersection Observer API 提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。通过这个 API 也可以实现图片懒加载,但是它的兼容性不是很好。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>图片懒加载</title>
        <style>
            .img-item {
                width: 100%;
                height: 700px;
            }
            .img-item > img {
                width: 100%;
                height: 100%;
            }
        </style>
    </head>
    <body>
        <div class="img-item">
            <img data-src="https://pic.netbian.com/uploads/allimg/211120/005250-1637340770973a.jpg" alt="1" />
        </div>
        <div class="img-item">
            <img data-src="https://pic.netbian.com/uploads/allimg/210920/165135-16321278956369.jpg" alt="2" />
        </div>
        <div class="img-item">
            <img data-src="https://pic.netbian.com/uploads/allimg/211122/223735-1637591855c6b1.jpg" alt="3" />
        </div>
        <div class="img-item">
            <img data-src="https://pic.netbian.com/uploads/allimg/211021/232902-16348301422b2a.jpg" alt="4" />
        </div>
        <script>
            const imgs = document.querySelectorAll('img');
            const imgList = Array.from(imgs);
        
            const options = {
                root: null,
                rootMargin: '0px',
                threshold: [0.15],
            };

            const ob = new IntersectionObserver(imgs => {
                // imgs 为目标元素集合
                imgs.forEach(item => {
                    // isIntersecting 代表目标元素可见
                    if (item.isIntersecting) {
                        console.log(item, '显示');
                        const img = item.target;
                        img.src = img.dataset.src;
                        ob.unobserve(img); // 停止对一个元素的观察
                    }
                });
            }, options);

            imgList.forEach(item => {
                // 可见性区域被监控的元素。此元素必须是根元素的后代 (如果根元素为视窗,则该元素必须被当前文档包含)。
                ob.observe(item);
            });
        </script>
    </body>
</html>

注意,dom结构中,我们给每个 img 元素添加一个父级元素并设置宽高。其目的,是因为加载 img 元素时,若是其 src 属性是空或不存在,就会变成一个非常小的正方形图标。这样的话,所有待加载的 img 就全部出现在可视区域之中,其结果就是加载所有图片而不是进行懒加载。

看下面这段代码:

imgList.forEach(item => {
    ob.observe(item);
});

这段代码就是为了监控每个出现在可视区域的元素以便执行回调,若是所有元素都出现在可视区域,那么它将性触发所有元素的回调,即加载所有的图片。