前端性能优化的关键策略:图片懒加载的进阶实践
前言
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