目录
一、什么是懒加载?
二、为什么要懒加载?
三、图片懒加载的实现原理
四、图片懒加载实现方式
3.1 方案一:设置 img 标签属性 loading=“lazy”
3.2 方案二:利用JS监听scroll滚动事件
3.3 方案三:利用元素的 getBoundingClientRect 方法实现
3.4. 方案四:IntersectionObserver
3.5 方案五:利用element-ui 的组件的lazy属性开启懒加载功能
3.6 方案六:使用vue-lazyLoad插件实现
一、什么是懒加载?
懒加载是一种对网页性能优化的方式,比如,当访问一个网页的时候,优先显示可视区域的图片而不是一次加载全部的图片,当需要显示时,再发送请求加载图片。
懒加载 :延迟加载,对于一个很长的页面,优先加载可视区域的内容,其他部分等进入可视区域时再加载。
二、为什么要懒加载?
懒加载是一种网页性能优化的方式,它能极大的提升用户体验。比如一个页面中有很多图片,但是首屏只出现几张,这时如果一次性把图片全部加载出来会影响性能。这时可以使用懒加载,页面滚动到可视区再加载。优化首屏加载。
一次性加载全部图片的话,一方面会影响用户体验;另一方面浪费用户的流量,有些用户并不想全部看完,全部加载会耗费大量流量。 而使用图片懒加载就会使页面加载速度快,减轻服务器压力,节约流量,用户体验好。
当页面需要展示大量图片时,如果一次性渲染所有图片,会向服务器发出大量请求,导致服务器响应慢,出现页面卡顿或崩溃等问题。采用懒加载技术只预先加载可视区内的图片,当滚动到其他位置时,才去加载这块区域的图片,也可以使用比较小的loading图片进行占位,有效减轻服务器的压力,加速页面渲染,提高用户体验。
小结——懒加载优点:
- 避免首次加载时消耗大量时间,降低页面渲染速度,造成卡顿现象。
- 按需加载,避免无效图片的加载,减轻服务器压力,节约网络资源。
三、图片懒加载的实现原理
- 基本原理: 监听图片是否位于页面的可视区域内,若在则加载图片,不在则不加载图片
- 实现方案: 自定义属性-将图片真实地址 url 存储在自定义属性中,当监听到图片进入可视区 域 时,将自定义属性值赋值给 img 的 src 属性
四、图片懒加载实现方式
图像是页面的一部分,它会提前开始加载。一旦浏览器在源代码中看到它们,就会提示下载。即使图像被隐藏,即使它位于一个非常长的页面的底部,也会提前加载。
3.1 方案一:设置 img 标签属性 loading=“lazy”
设置 loading="lazy"允许浏览器,延迟加载屏幕外图像 img 和 iframe,直到用户滚动到它们附近。
loading属性支持下面这几个值:
① lazy:图片或框架懒加载,也就是元素资源快要被看到的时候加载。
② eager:图片或框架无视一切进行加载。
③ auto:默认值。
图片或框架基于浏览器自己的策略进行加载。如果HTMLImageElement或者HTMLIFrameElement元素没有显式地设置loading属性或者loading属性的值不合法,则都被当作’auto’处理。
<img src="" loading="lazy"/>
<iframe src="" loading="lazy"/>
- Lazy loading加载数量与屏幕高度有关,高度越小加载数量越少,但并不是线性关系。
- Lazy loading加载数量与网速有关,网速越慢,加载数量越多,但并不是线性关系。
- 滚动即会触发图片懒加载,而不是滚动一屏后再去加载。
- 窗口resize尺寸变化也会触发图片懒加载,当屏幕高度从小变大的时候。
3.1.1 loading 属性的兼容
loading 属性的兼容性如下:
3.1.2 如何判断当前浏览器是否支持loading=”lazy”?
下面三种方法都可以:
var isSupportLoading = 'loading' in document.createElement('img');
var isSupportLoading = 'loading' in new Image();
var isSupportLoading = 'loading' in HTMLImageElement.prototype;
例:您可以通过检查HTMLImageElement.prototype
上是否存在loading
属性来判断当前浏览器是否支持loading="lazy"
特性。以下是一个简单的JavaScript代码示例:
function isLazyLoadingSupported() {
// 检查'loading'属性是否在HTMLImageElement.prototype上定义
return 'loading' in HTMLImageElement.prototype;
}
if (isLazyLoadingSupported()) {
console.log('Lazy loading is supported.');
} else {
console.log('Lazy loading is not supported.');
}
如果isLazyLoadingSupported()
函数返回true
,则当前浏览器支持loading="lazy"
;如果返回false
,则不支持。
3.1.3. 如何获取loading属性值
var attrLoading = img.loading;
如果浏览器并不支持原生的loading懒加载,则会返回undefined,例如在Firefox浏览器下
3.2 方案二:利用JS监听scroll滚动事件
3.2.1. 实现原理
监听scroll事件,获取 img 元素相对于视口的顶点位置 el.getBoundingClientRect().top,只要这个值小于浏览器的高度 window.innerHeight 就说明进入可视区域,当图片进入可视区域时再去加载图片资源。
图片刚开始显示的是默认图片,当进入可视区域时,用 data-src 的真实图片地址赋值给 src。
3.2.2. 实现方案
① 首先给图片一个占位资源:
<img src="default.jpg" data-src="http://www.xxx.com/target.jpg" />
② clientHeight、scrollTop 和 offsetTop
属性介绍
获得浏览器窗口(视口)大小: 2组:
1、获取文档显示区 / 网页可见区域大小
① 对于Internet Explorer9+、Chrome、Firefox、Opera 以及 Safari
window.innerWidth、window.innerHeight | Window innerWidth 与 innerHeight 属性 | 参考手册
window.innertHeight : 浏览器窗口的内部高度
- A 表示window的内部高度,包含border、padding、margin以及水平滚动条的高度
- B 语法:window.innertHeight
window.innerWidth: 浏览器窗口的内部宽度
- A 表示window的内部宽度,包括 border、padding、margin以及垂直滚动条的宽度
- B 语法:window.innerWidth
② 对于 Internet Explorer 8、7、6、5
element.clientHeight、element.clientWidth
document.body.clientHeight : 表示的是<body>元素的内容区域高度
- A 表示的是<body>元素的内容区域高度,包含padding 不包含 border 和 margin以及水平滚动条的高度
- B 语法:element.clientHeight
document.body.clientWidth : 表示的是<body>元素的内容区域宽度
- A 表示的是<body>元素的内容区域宽度,包含padding 不包含 border 和 margin以及垂直滚动条的宽度
- B 语法:document.body.clientWidth
或者
document.documentElement.clientHeight:表示的是整个视口的高度
- A 表示的是整个视口的高度,即整个网页的可视区域高度,包含padding 不包含 border 和 margin以及水平滚动条的高度
- B 语法:document.documentElement.clientHeight
document.documentElement.clientWidth:表示的是整个视口的宽度
- A 表示的是整个视口的宽度,即整个网页的可视区域宽度,包含padding 不包含 border 和 margin以及垂直滚动条的宽度
- B 语法:document.documentElement.clientWidth
参考:JS | 深入理解客户区尺寸client
✔ 实用的获取页面大小的浏览器兼容性JavaScript 方案(涵盖所有浏览器):
var w=window.innerWidth || document.documentElement.clientWidth
|| document.body.clientWidth;
var h=window.innerHeight || document.documentElement.clientHeight
|| document.body.clientHeight;
补:document.documentElement.clientHeight与document.body.clientHeight的主要区别在于它们代表的网页区域不同。
- document.documentElement.clientHeight 表示的是整个视口的高度,即整个网页的可视区域高度。它包括了padding但排除了border、水平滚动条和margin的高度。
- document.body.clientHeight 表示的是<body>元素的内容区域高度,通常不包括滚动条的高度。如果<body>元素没有设置高度或者其高度小于其内部内容的高度,则可能会显示不全。
影响这两个属性值的主要因素包括:
- CSS设置:如果<body>元素的CSS高度被设置为100%,并且包含了所有的内容高度,那么document.body.clientHeight将反映整个视口的高度。相反,如果<html>元素的CSS高度被设置为100%,而<body>元素的高度未明确设置,那么document.documentElement.clientHeight将反映整个视口的高度,而document.body.clientHeight可能为0或小于视口高度。
- DOCTYPE声明:缺少DOCTYPE声明可能会导致document.documentElement.clientHeight为0,因为浏览器可能无法正确解析文档类型,从而影响元素的默认显示和尺寸计算。
实际应用中的注意事项:
- 在开发过程中,应注意DOCTYPE的声明和CSS的设置,以确保页面布局的正确显示。如果需要获取整个视口的高度,应使用document.documentElement.clientHeight;如果需要获取<body>元素的内容高度,应确保<body>元素的CSS高度设置正确。
- 在调试页面布局时,可以使用浏览器的开发者工具查看这些属性的值,帮助定位问题所在
参考:document.documentElement.clientHeight与document.body.clientHeight的区别-CSDN博客
补:document.body.clientWidth和window.innerWidth的主要区别在于是否包含滚动条和边框。
document.body.clientWidth
表示浏览器窗口中文档主体部分的视口宽度,这个值包括了文档主体的内容区域宽度,但不包括滚动条的宽度。如果文档主体有边框,这个值也不包括边框的宽度。相反,window.innerWidth
表示浏览器窗口的内视口宽度,这个值包括了视口内部的全部宽度,即包括了滚动条的宽度(如果存在的话)。
此外,document.body.clientWidth
和window.innerWidth
的区别还受到
文档模式的影响。在混杂模式(BackCompat)下,document.body.clientWidth
和window.innerWidth
的值可能不同,因为混杂模式下文档主体的宽度计算不包括滚动条。而在标准模式(CSS1Compat)下,两者的值通常是相同的,因为此时文档的宽度计算包括了滚动条
2、完整窗口大小: window.outerWidth window.outerHeight
offsetTop : 距离父级元素顶部的高度
- A 表示当前元素相对于其offsetParent元素的顶部内边距的距离
- B 语法:element.offsetTop
scrollTop : 网页被卷去的距离
A 表示在有滚动条时,滚动条向下滚动的距离也就是元素顶部被遮住部分的高度
B 语法:element.scrollTop
③ 接着,通过监听 scroll 事件来判断图片是否到达视口
let img = document.getElementsByTagName("img");
let num = img.length;
let count = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
lazyload(); // 首次加载别忘了显示图片
window.addEventListener('scroll', throttle(lazyload, 200)); // throttle是节流函数,自己实现一下
//offsetTop是元素与offsetParent的距离,循环获取直到页面顶部
function getTop(el) {
var T = el.offsetTop;
while(el = el.offsetParent) {
T += el.offsetTop;
}
return T;
}
function lazyload() {
let viewHeight = document.documentElement.clientHeight || document.body.clientHeight;//视口高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;//滚动条卷去的高度
for(let i = count; i <num; i++) {
// 元素现在已经出现在视口中
if(getTop(img[i]) < scrollTop + viewHeight) {
if(img[i].getAttribute("src") !== "default.jpg") continue;
img[i].src = img[i].getAttribute("data-src");
count ++;
} else {
break; // 提早退出
}
}
}
offsetParent 就是距离该子元素最近的进行过定位的父元素(position:absolute或relative 或fixed),如果其父元素中不存在定位,则offsetParent为:body元素
参考:JS | JS之元素偏移量 offset 系列属性详解
●小结:用JS监测这个方式主要是利用整体距离实现
(1)属性介绍:参看上面
(2)实现方法
可以用image.offsetTop <= document.documentElement.clientHeight + document.documentElement.scrollTop 判断图片是否可以在可视区域内。即判断图像的offsetTop(距离顶部的高度)- scrollTop(页面被卷去的高度)<=window.innerHeight(可视区高度)才加载
- 图片元素位置的顶部距离:offsetTop
- 滚动距离的最下端:scrollTop+clientHeight
// html 标签结构
<img data-src="./public/image/VCG211430870249.jpg" src="./public/image/默认.jpg" alt="">
<img data-src="./public/image/VCG211430987515.jpg" src="./public/image/默认.jpg" alt="">
<img data-src="./public/image/VCG211431054751.jpg" src="./public/image/默认.jpg" alt="">
<img data-src="./public/image/VCG211435102490.jpg" src="./public/image/默认.jpg" alt="">
<img data-src="./public/image/VCG211438229829.jpg" src="./public/image/默认.jpg" alt="">
<img data-src="./public/image/VCG211438109615.jpg" src="./public/image/默认.jpg" alt="">
<script>
// 1 获取全部图片的DOM节点
// 注意:querySelectorAll 值为伪数组利用扩展运算符转为真数组
const images = [...document.querySelectorAll('img')]
// 2 监听页面滚动事件
window.addEventListener('scroll', lazyLoad)
// 3 定义页面滚动的处理函数
function lazyload(e) {
// 3.1 获取屏幕的可视高度
const clientHeight = document.documentElement.clientHeight
// 3.2 获取屏幕的滚动距离
const scrollTop = document.documentElement.scrollTop
for (let i = 0; i < images.length; i++) {
if (images[i].offsetTop < clientHeight + scrollTop) {
images[i].setAttribute('src', images[i].getAttribute('data-src'))
}
}
}
</script>
3.3 方案三:利用元素的 getBoundingClientRect 方法实现
getBoundingClientRect() 方法返回元素相对视口左上角的偏移量以及元素本身长宽,单位:px。
修改上述 lazyload 函数
function lazyload() {
let viewHeight = document.documentElement.clientHeight || document.body.clientHeight;
for (let i = count; i < num; i++) {
// 元素现在已经出现在视口中
if (img[i].getBoundingClientRect().top < viewHeight) {
if (img[i].getAttribute("src") !== "default.jpg") continue;
img[i].src = img[i].getAttribute("data-src");
count++;
} else {
break;
}
}
}
小结:我们再来举个例子,总结一下利用getBoundingClientRect 方法实现图片懒加载。
参考:JS | JS中的getBoundingClientRect()方法详解,秒懂!
(1)属性介绍:
利用 getBoundingClientRect()方法
实时获取物体的动态位置
(2)实现方法:
步骤 1:监听页面滚动事件,lazyLoad
为页面滚动时的处理函数,在本节为处理图片懒加载。
window.addEventListener('scroll', lazyLoad)
步骤 2:判断图片是否处于可视区域内
参考:JS代码如何利用getBoundingClientRect方法实现图片懒加载 – PingCode
(1)若距离顶部top(或bottom)小于页面的整体高度window.innerHeight
(2)若距离左侧left(或右侧right)小于页面的整体宽度window.innerWidth
(3)同时图片的底部bottom (或顶部top) 与图片的右侧right(左侧left)距页面顶部、左侧的距离均大于0,则说明该图在屏幕的可视区域内。
为了提高复用性,我们可以将它封装成一个自定义函数isVisible
,将每张图片作为参数传入该函数,并返回true
或false
// 可视区域判断函数
function isVisible(img) {
// 判断是否在可视区域,并返回true或false
const imgRect = img.getBoundingClientRect() // getBoundingClientRect 获取图片的动态信息
return imgRect.bottom > 0 && imgRect.top < (window.innerHeight|| document.documentElement.clientHeight) && imgRect.right > 0 && imgRect.left < (window.innerWidth||document.documentElement.clientWidth)
}
步骤 3:定义图片懒加载时的处理事件,监听所有的img
,判断该img
是否处于可视范围内。
querySelectorAll
获取的元素为类数组对象,需要转为真数组,否则无法使用数组的某些方法
// 获取所有的img元素,并利用扩展运算符转为真数组
const images = [...document.querySelectorAll('img')]
步骤 4:对每张图片进行监听,利用自定义函数isVisible判断是否在可视区域内
(1)若处于可视区域:将自定义的data-src值,赋值给真正的src属性值,其中 data-src存储图片的URL地址,并删除该元素防止重复加载
(2)若不处于可视区域:return 不做处理
// 利用循环判断每张图片是否属于可视区域
function lazyLoad(){
for (let i = 0; i < images.length; i++) {
// isVisible是否该图片位于可视区域 返回true 或false
if (isVisible(images[i])) {
// 将元素的自定义属性 data-src 赋值给元素的 src 属性
// 等价于:img.setAttribute('src', img.getAttribute('data-src'))
images[i].src = images[i].dataset.src
// 防止重复被遍历 加载完之后 删除元素不再加载
images.splice(i, 1)
i--
}
}
}
lazyLoad()
getBoundingClientRect()方法的缺点:这个属性频繁计算会引发页面的重绘,可能会对页面的性能造成影响。
3.4. 方案四:IntersectionObserver
有兼容性问题。这是浏览器内置的一个API,实现了监听window的scroll事件、判断是否在视口中以及节流三大功能。
let img = document.getElementsByTagName("img");
const observer = new IntersectionObserver(changes => {
//changes 是被观察的元素集合
for(let i = 0, len = changes.length; i < len; i++) {
let change = changes[i];
// 通过这个属性判断是否在视口中
if(change.isIntersecting) {
const imgElement = change.target;
imgElement.src = imgElement.getAttribute("data-src");
observer.unobserve(imgElement);
}
}
})
Array.from(img).forEach(item => observer.observe(item));
这样就很方便地实现了图片懒加载,当然这个IntersectionObserver也可以用作其他资源的预加载,功能非常强大。原文链接:https://blog.csdn.net/qq_44741577/article/details/139324747
小结:关于IntersectionObserver
Intersection Observer
是一个比较新的api,他允许你追踪目标元素与其祖先元素或视窗的交叉状态,用他来检测图片是否进入视口非常方便,不用再像之前绑定事件、计算距离等。
(1)属性介绍:
- 利用Intersection Observer实例上的observe和unobserve方法,注册或取消监听事件。
- 利用isIntersecting方法,判断该图片是否处于图片与屏幕可视区域的交叉范围内。
- 注意:Intersection Observer实例会监听交叉状态,即出现和消失(触发两次),出现交叉状态后会去调用new的时候传入的callback回调函数
(2)实现方法:
步骤 1: 监听页面滚动事件,lazyLoad
为页面滚动时的处理函数,在本节为处理图片懒加载。
window.addEventListener('scroll', lazyLoad)
步骤 2: 创建图片与可视区域交叉实例
callback
:
- 此为传入的回调函数,用于当处于交叉状态改变时进行的处理函数
- 该函数会被触发2次:图片进入视野时+图片离开视野时
const observer = new IntersectionObserver(callback)
步骤 3: 利用observer
实例上的.observe(img)
方法,给每张图片绑定观察事件
// 给每一个图片绑定观察方法
imagess.forEach(img => {
// 图片进入视野+离开视野时会触发callback回调函数
observer.observe(img)
})
步骤 4: 定义图片的懒加载事件
imgArr
:
- 可以获得包含所有图片的
isIntersecting
属性的集合,该属性可判断是否在交叉区域内 target
为该图片的标签元素
// callback 接收的参数为带有监听所有图片交叉属性的集合
const callback = (imgArr) => {
console.log("视图交叉时触发,离开交叉时也触发", imgArr);
imgArr.forEach((e) => {
// 判断是否在视野区域
if (e.isIntersecting) {
e.target.src = e.target.dataset.src;
// 取消监听,避免重复加载同一张图片
observer.unobserve(e.target);
}
});
};
(3)完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<img data-src="./public/image/VCG211430870249.jpg" src="./public/image/默认.jpg" alt="" />
<img data-src="./public/image/VCG211430987515.jpg" src="./public/image/默认.jpg" alt="" />
<img data-src="./public/image/VCG211431054751.jpg" src="./public/image/默认.jpg" alt="" />
<img data-src="./public/image/VCG211435102490.jpg" src="./public/image/默认.jpg" alt="" />
<img data-src="./public/image/VCG211438229829.jpg" src="./public/image/默认.jpg" alt="" />
<img data-src="./public/image/VCG211438109615.jpg" src="./public/image/默认.jpg" alt="" />
<script>
// intersectionObserver 交叉观察 : 目标元素和可视窗口会产生交叉区域
const imagess = [...document.querySelectorAll("img")];
// 2.1 创建视觉交叉的观察实例
const observer = new IntersectionObserver(callback);
// 2.2 给每一个图片绑定观察方法
imagess.forEach((img) => {
// 2.3 图片进入视野+离开视野时触发 - 回调
observer.observe(img);
});
// callback 接收的参数为带有监听所有图片交叉属性的集合
const callback = (imgArr) => {
console.log("视图交叉时触发,离开交叉时也触发", imgArr);
imgArr.forEach((e) => {
// 判断是否在视野区域
if (e.isIntersecting) {
e.target.src = e.target.dataset.src;
// 取消观察追踪,避免重复加载同一张图片
observer.unobserve(e.target);
}
});
};
</script>
</body>
</html>
参考:JS | 图片懒加载之交叉观察器IntersectionObserver API 手把手教学 - 烤地瓜的CSDN博客
3.5 方案五:利用element-ui 的<el-image>组件的lazy属性开启懒加载功能
使用element-ui 的<el-image v-for="url in urls" :key="url" :src="url" lazy></el-iamge>可通过lazy开启懒加载功能,当图片滚动到可视范围内才会加载。
具体实现代码:element-ui的 <el-image> Element - The world's most popular Vue UI framework
<div class="demo-image__lazy">
<el-image v-for="url in urls" :key="url" :src="url" lazy></el-image>
</div>
<script>
export default {
data() {
return {
urls: [
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg'
]
}
}
}
</script>
3.6 方案六:使用vue-lazyLoad插件实现
使用vue-lazyLoad插件实现,安装【npm i vue-lazyload -S】,main.ts中进行注册
//main.ts
import VueLazyload from 'vue-lazyload'
app.use(VueLazyload,
{preLoad: 1.3, //预加载的宽高比loading:
loadimage:"./assets/vue.svg", //图片加载状态下显示的图片
// error: "", //图片加载失败时显示的图片
attempt: 1, // 加载错误后最大尝试次数
})
页面中通过v-lazy使用
//xx.vue
<template>
<div>
<div v-for="item in arr" :key="item">
<img v-lazy="item" width="600" height="200" alt="" />
</div>
</div>
</template>
<script lang="ts" setup>
//glob是懒加载模式,globEager静态加载
let imageList: Record<string, { default: string }> = import.meta.glob(
"../assets/images/*.*",
{ eager: true }
);
let arr = Object.values(imageList).map((v) => v.default);
</script>
<style lang="scss" scoped></style>
使用vite的两种文件导入方式
- 【import.meta.glob("/xx")】方式 为懒加载模式,动态导入,构建时,会分离为独立的 chunk,该方式导入使用()=>import("/xx"),跟路由懒加载是一样的导入方式。
- 【import.meta.globEager("/xx")】以及【import.meta.glob("/xx",{eager:true})】为静态加载
打印imageList如图
● 参考资料 ●
实现图片懒加载的5种方式-CSDN博客 | JS实现图片懒加载的三种常用方法总结-CSDN博客
图片懒加载🔥三种实现方案 _ 掘金 | JS代码如何实现图片懒加载 – PingCode
如何使用HTML、CSS和jQuery实现图片懒加载的进阶技巧-js教程-PHP中文网
JS | JS中的getBoundingClientRect()方法详解,秒懂!- 烤地瓜的CSDN博客
JS | 图片懒加载之IntersectionObserver API 手把手教学 - 烤地瓜的CSDN博客