图片懒加载

懒加载


介绍

懒加载究竟是什么呢,其实我一开始也没搞懂。以为是对于图片或者视频等媒体资源,让它进入视口才去请求资源减少加载网页需要的网络带宽存储,并加快页面渲染
那么对于文字呢,我觉得就几KB的文字用懒加载也是没必要,不够后来看了一下MDN上的介绍大致了解了它的含义。

延迟加载(懒加载)是一种将资源标识非阻塞(非关键)资源,并仅在需要时加载它们的策略。
这是一种缩短关键渲染路径长度的方法,可以缩短页面加载时间
延迟加载可以在应用程序的不同时刻发生,但通常会在某些用户交互(例如滚动和导航)上发生

  • 大致意思是是将非关键资源在需要时才加载,缩短加载事件。

在解析 HTML 时会创建DOM。HTML可以请求JavaScript,进而可能反过来修改DOM
HTML也可以包含或请求样式,进而构建CSSOM
浏览器引擎将两者结合起来以创建渲染树,进而确定布局页面上所有内容的大小和位置。
布局明确后,浏览器就会将像素绘制到屏幕上。

  • 大致意思是HTML解析时,DOM和JS以及CSS都将消耗时间,直到它们完成。

所以非关键资源的懒加载如下:

  1. Javascript:将暂时不需要的外部库在页面加载后执行,只需通过async:
    • <script src="main.js" async></script>
  2. CSS:在<head>中加载CSS,尽量避免使用@import,CSS文件尽量不超过30KB,对于大量相同的样式应该设置为通用。
  3. 文字样式:可以使用 <link rel="preload">CSS font-display 属性字体加载 API 来覆盖默认行为并预加载网络字体资源。

图片懒加载

前面介绍了这么多,而图片懒加载其实已经有现成属性方法,又或者UI组件已经自行封装好了,不过我还是讲一下在以前经常使用的方法或者是现在的懒加载使用方法

1. loading属性

其实就是在<img Loading=""/>上一个属性,指示浏览器应当如何加载该图像。
允许的值:

  • eager:立即加载图像,不管它是否在可视视口(visible viewport)之外(默认值)。
  • lazy:延迟加载图像,直到它和视口接近到一个计算得到的距离(由浏览器定义)。

2. 监听滚动行为并实时计算(不建议)

基础原理:

从图片懒加载的思想来看,就是指将用户还看不到的图片不发起图片请求,直到能看到时发起请求
那么该怎么确定图片是否出现在视口呢,这时用之前无限滚动中的方法原理即可。

  1. 获取带有滚动条div的DOM:用于取得视口大小距离顶端滚动距离,以及监听滚动事件
  2. 获取img的DOM:用于取得其距离滚动div顶端的距离,以及加载图片
  3. 触发滚动时计算:距离滚动div顶端的距离-距离顶端滚动距离>=视口大小

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<template>
<div style="display: flex; flex-direction: column">
<!-- 滚动懒加载开始 -->
<div
class="scroll_container"
id="scrollableDiv"
v-show="functionIndex === 0"
>
<template v-for="i in 20">
<div class="temp_div" v-if="i < 10" :key="i"></div>
<img
src=""
data-src="https://img.1tupian.com/preimg/DBC456/1200/DBC456/154/37773021eceeb6e3191cb5b2639d9856.png"
alt=""
v-else
:key="'_' + i"
class="scroll_img"
/>
</template>
</div>
</div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
const functionIndex = ref(0)
onMounted(() => {
//#region 滚动实现懒加载开始
// 1. 获取带有滚动条的div的DOM
const ScrollContainer = document.getElementById('scrollableDiv')
// 2. 获取img的DOM
const ScrollImgList = document.querySelectorAll('.scroll_img')
// 3.触发滚动时触发
ScrollContainer?.addEventListener('scroll', () => {
const scrollTop = ScrollContainer.scrollTop // 获取滚动距离顶端高度
const divHeight = ScrollContainer.clientHeight // 获取div高度
// 循环遍历img列表,确认是否显示
ScrollImgList.forEach((imgDom, index) => {
if (
divHeight + scrollTop >= imgDom.offsetTop &&
imgDom.getAttribute('src')?.length === 0
) {
console.log(`第${index}个图片出现在了视口中!!!`)
//替换图片src
imgDom.src = imgDom.dataset.src
}
})
})
//#endregion 滚动实现懒加载结束
})
</script>

<style scoped>
.scroll_container {
width: 100%;
height: 600px;
background-color: yellowgreen;
overflow: auto;
position: relative;
border: 1.5px solid black;
}
.temp_div {
width: 100%;
height: 300px;
background-color: red;
border: 1px black solid;
}
.scroll_img {
width: 100%;
height: 300px;
}
</style>

完善与思考

问题:

  • Element.getBoundingClientRect() 方法是否可以:我看教程时发现有用这个方法的,不过在我查阅了MDN上这个方法时发现,其提供了元素的大小及其相对于视口的位置,也就是被body包裹的才是视口,所以如果滚动条是body的则那么可以用,只需判断返回的 DOMRect对象中top= = =视口高度即可。值得注意的一点是,在Vue文件中视口则是其最外层元素,所以创建一个单独vue组件来做也是一种方法
  • 对于操作DOM上属性的方法:原生属性直接.属性名即可,非原生的则需data-属性名设置,并且通过.dataset.属性名.getAttribute('data-属性名')操作即可。

3. 使用交叉观察器

基础原理:

  1. 获取观察目标列表:用于将其中元素监听
  2. 创建交叉观察器:用于其进入视口离开视口时执行方法。
  3. 监听观测目标列表中每一个DOM元素:当其进入视口时执行对于方法

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template>
<!-- 交叉观察懒加载开始 -->
<div class="scroll_container" id="root" v-show="functionIndex === 1">
<template v-for="i in 20">
<div class="temp_div" v-if="i < 10" :key="i"></div>
<img
id="target"
src=""
data-src="https://img.1tupian.com/preimg/DBC456/1200/DBC456/154/37773021eceeb6e3191cb5b2639d9856.png"
alt=""
v-else
:key="'_' + i"
:data-index="i"
class="scroll_img"
/>
</template>
</div>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
const functionIndex = ref(0)
onMounted(() => {
//#region 交叉观察器懒加载开始
// 1.获取观察目标列表
const targetElementList = document.querySelectorAll('#target')
// 2.创建交叉观察器
const observer = new IntersectionObserver(entries => {
//循环entries数组,对每个观察目标的IntersectionObserverEntry对象进行操作
entries.forEach(item => {
// 通过isIntersecting返回的布尔值判断是否相交
if (item.isIntersecting) {
//通过.target方法获得观察目标DOM并更改src值
item.target.src = item.target.dataset.src
observer.unobserve(item.target) // 停止观察当前元素 避免不可见时候再次调用callback函数
console.log(item.target.dataset.index + '图片已加载')
}
})
})
// 3.监听观测目标列表中每一个DOM元素
targetElementList.forEach(item => {
observer.observe(item)
})
// observer.disconnect() // 离开页面或者不需要observer时
//#endregion 交叉观察器懒加载结束
})
</script>

<style scoped>
.scroll_container {
width: 100%;
height: 600px;
background-color: yellowgreen;
overflow: auto;
position: relative;
border: 1.5px solid black;
}
.temp_div {
width: 100%;
height: 300px;
background-color: red;
border: 1px black solid;
}
.scroll_img {
width: 100%;
height: 300px;
}
</style>

完善与思考

问题:

  1. 通过这种方法实现其实也没什么问题,我在看现成的UI组件库时,发现懒加载都是用这种方法实现,可见交叉观察器实用
    思考:
  2. 其实其它方法也可以用,例如先前说过的流媒体,只需将其放入div中。