likes
comments
collection
share

前端性能优化的关键策略:图片懒加载的进阶实践

作者站长头像
站长
· 阅读数 23

前言

loading="lazy"属性

HTML5出现以后,浏览器开始支持loading="lazy"属性用于图片懒加载了,这个属性使用起来非常简单,我们只要在 img 标签中设置 loading="lazy" 属性即可,不需要将真实地址暂存在一个自定义的数据属性data-src中。浏览器会自动管理图片的加载时机,只有当图片接近进入视口时才开始加载。

<!-- 使用 loading="lazy" 属性,src 属性直接指向真实图片 -->
<img src="https://img.36krcdn.com/20190808/v2_1565254363234_img_jpg" loading="lazy" alt="Image 1">

这种方法的优势在于实现起来非常简单,我们无需编写任何额外的JavaScript代码。缺点在于这个属性的功能较为单一,只适用于基本的懒加载场景,而且在一些较旧的浏览器中可能不被支持。

IntersectionObserver API

IntersectionObserver API是一种浏览器提供的API,用于监控元素什么时候进入或离开视口可视区域。它通过异步的方式工作,避免了使用轮询或频繁的DOM查询,比起其他方法在性能上具有显著优势。这个API主要由两个部分组成:IntersectionObserver构造函数和一个回调函数。当被观察的元素与视口的交集发生变化时,回调函数就会被触发。接下来我将手写一个利用IntersectionObserver API实现图片懒加载的方法来展示以便于你们更好了解。

首先我们先写一个简单的html页面来模拟展示多张图片,并将图片的真实地址(src)暂时存储在一个自定义的数据属性(data-src)中,将占位图的地址赋值给src属性:

<!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>
        img {
            display: block;
            margin-bottom: 50px;
            width: 400px;
            height: 400px;
        }
    </style>
</head>
<body>
    <!-- 使用 data-src 存储图片的真实URL,src 使用占位图 -->
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://ts4.cn.mm.bing.net/th?id=ORMS.a53c0a4415731ddbd6da9a815eb2b57b&pid=Wdp&w=90&h=90&qlt=90&c=1&rs=1&dpr=1.25&p=0" />
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190808/v2_1565254363234_img_jpg">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190905/v2_1567641293753_img_png">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190905/v2_1567640518658_img_png">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190905/v2_1567642423719_img_000">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190905/v2_1567642425030_img_000">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190905/v2_1567642425101_img_000">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190905/v2_1567642425061_img_000">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190904/v2_1567591358070_img_jpg">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190905/v2_1567641974410_img_000">
    <img  src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png" data-src="https://img.36krcdn.com/20190905/v2_1567641974454_img_000">
</body>
</html>

1. 创建观察器

在JavaScript中,首先需要创建一个IntersectionObserver实例。这个实例将负责监听DOM元素的可见性变化。观察器接受一个回调函数作为第一个参数,这个函数将在DOM元素的可见性变化时被调用。第二个参数是一个可选的配置对象,用于自定义观察器的行为,包括阈值(threshold)、根元素(root)、根元素的边界框(rootMargin)等。其中threshold的属性是一个0到1之间数字的数组,属性是单个数字(写成单个数字时,它会被自动转换为一个只包含该数字的数组),或者是包含在一个数组中的多个数字,用于控制回调的触发时机。

<script>
        // 创建 IntersectionObserver 实例
        const observer = new IntersectionObserver((entries) => {
            // 回调函数
            // ......
        }, {
            threshold: 0.5 // 当元素至少50%可见时触发回调
        });
        
        // 观察目标
        // ......
</script>

2. 指定观察目标

选取所有要加载的图片,观察它们是否进入可视区域。

// 选取所有需要懒加载的图片
const imgs = document.querySelectorAll('img');

// 观察所有图片
imgs.forEach(img => observer.observe(img));

3. 加载逻辑

当一张图片的至少50%进入视口后触发回调函数,将data-src属性中的真实url赋值给img元素的src属性。

// 回调函数
entries.forEach(entry => {
    if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.getAttribute('data-src'); // 加载真实图片
        observer.unobserve(img);                // 图片加载后,不再观察
    }
});

进阶版:手写一个hook函数实现“加载更多”

以下代码定义了一个 Vue.js 的组合式 API hook 函数,主要功能是利用 IntersectionObserver API 来检测页面上的某个元素(通过 ref 提供)是否进入了可视区域,并在元素可见时调用一个加载更多的函数。

import { ref, watch, onUnmounted, Ref } from 'vue'

/**
 * @func: 滚动到底部或最右边加载更多
 * @desc: 是否滚动到底部?IntersectionObserver 监听是否进入可视区域
 * @param node - 通过 ref 获取的 DOM 节点,指向列表最后一个元素。
 * @param loadMore - 当元素进入可视区域时调用的函数,用于加载更多的数据。
 * @returns - 返回一个对象,包含是否还有更多数据的状态和更新该状态的方法。
 */

export const useIntersectionObserver = (node: Ref<HTMLElement | null>, loadMore: () => void ) => {
    const hasMore = ref<boolean>(true) // 状态变量,用于跟踪是否还有更多数据可加载

    // 创建 IntersectionObserver 实例
    let observer: IntersectionObserver

    // 监听 node ref 的变化,当 ref 变更时更新 IntersectionObserver
    watch(node, (newRef, oldRef) => {
        // 如果有旧的 ref,先从旧的 ref 上移除观察
        if (oldRef && observer) {
            observer.unobserve(oldRef)
        }

        // 如果新的 ref 存在,则创建观察者
        if (newRef) {
            observer = new IntersectionObserver(([entry]) => {
                // 如果元素进入可视区域
                if (entry.isIntersecting) {
                    loadMore()  // 调用 loadMore 函数加载更多数据
                }
            }
            observer.observe(newRef)
        }
    })

    // 在组件卸载时清理 IntersectionObserver
    onUnmounted(() => {
        if (observer) {
            observer.disconnect()
        }
    })

    // 监听 hasMore 的变化,根据 hasMore 的值控制观察者是否继续工作
    watch(hsaMore, (value) => {
        value ? observer.observe(node.value!) : observer.disconnect()
    })

    // 返回 hasMore 状态和更新方法
    return {
        hasMore,
        setHasMore: (value: boolean) => {
            hasMore.value = value
        }
    }
}

总结

loading="lazy" 属性简单易用,IntersectionObserver API 则提供了更为灵活和强大的解决方案,适用于各种复杂的懒加载和动态加载场景。这两种图片懒加载的方法都可以显著减少页面加载时间,提升用户体验。希望在阅读完本文后能帮助你更好地理解和实现图片懒加载技术,为你的项目带来更好的性能优化效果,感谢观看 ♪(・ω・)ノ

转载自:https://juejin.cn/post/7395024434681856051
评论
请登录