写在前面
上一篇文章介绍了防抖和节流
,那么今天我们就来康康图片懒加载(中间会涉及到节流
的优化问题)
你将了解到图片懒加载
的实现的三种方法(整体位置比较和视图位置比较和observe
)
和优化的两种方式(节流
和IntersectionObserver
)
写得不对的地方,希望大家能够批评指正!
在不使用图片懒加载的情况下,我们打开开发者工具康康
- 这里
display
给block
是为了让图片变成块元素,这样图片会自己换行,这样更方便后面进行懒加载的操作 - 注意勾选
禁用缓存
,和慢速3G
,这样效果更加明显

1`<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge">
7 <meta name="viewport" content="width=device-width, initial-scale=1.0">
8 <title>Document</title>
9 <style> img {
10 display: block;
11 width: 100px;
12 height: 100px;
13 margin-top: 20px;
14 } </style>
15</head>
16
17<body>
18 <p>今天介绍一下图片懒加载</p>
19 <img src="./pic/1.png" alt="">
20 <img src="./pic/2.png" alt="">
21 <img src="./pic/3.png" alt="">
22 <img src="./pic/4.png" alt="">
23 <img src="./pic/5.png" alt="">
24</body>
25
26</html>`
27
28
- 我们很容易看出,由于图片的加载时间很长,首屏的渲染用了
7s
完成 - 下面我们就来看看懒加载和懒加载的优化吧!
- 康康最后通过优化能提升多少性能!
什么是懒加载?
这里就来介绍一下懒加载
- 简单来说,就是懒惰的加载
明日复明日
- 在首屏渲染,如果我的可视区域里面看不到图片,那就先不加载图片
- 这种合理的偷懒就是懒加载,它大大缩减了首屏的渲染时间
懒加载的前两种实现方法(位置判断)
- 获取图片元素,图片的
src
属性改为data-src
,即src
属性为空
1`<img data-src="./pic/1.png" alt="">
2 <img data-src="./pic/2.png" alt="">
3 <img data-src="./pic/3.png" alt="">
4 <img data-src="./pic/4.png" alt="">
5 <img data-src="./pic/5.png" alt="">`
- 添加滚动事件监听,判断图片位置和当前位置来给
src
赋值,从而达到了动态加载图片的效果 - 这个距离的判断又有大概两种,下面来分析一下
1.通过整体距离来判断
- 通过下图,我们可以知道
- 一个图片元素的位置的顶部可以用
offsetTop
属性获得 - 如果把
div
换成文档对象,scrollTop+clientHeight
就可以表示滚动距离的最下端 - 故
offsetTop<scrollTop+clientHeight
的时候我们就要改变图片的src
了 - 注意上面的
offsetTop
和后面的scrollTop+clientHeight
是针对不同的元素哦,前者是图片,后者是文档对象 document.documentElement
会返回一个文档对象
- 一个图片元素的位置的顶部可以用
- 故代码如下:
1`const images = document.querySelectorAll('img')
2 n = 0
3 let lazyload = (e) => {
4 const clientHeight = document.documentElement.clientHeight
5 const scrollTop = document.documentElement.scrollTop
6 for (let i = n; i < images.length; i++) {
7 if (images[i].offsetTop < clientHeight + scrollTop) {
8 images[i].setAttribute('src', images[i].getAttribute('data-src'))
9 n = i + 1
10 }
11 }
12 console.log('scroll触发');
13 }`
2.通过视口距离来判断
- 与上面的方法不同的是,我们不去管整体滚动了多少,图片相对于整体的offset位置是多少
- 我们只去关心视口的距离
getBoundingClientRect().top
可以帮我们获得图片相对于视口距离顶部的距离window.innerHeight
可以帮我们获得视口的高度(一般来说,对一个设备来说是一个固定值!)
1`const images = document.querySelectorAll('img')
2 let n = 0
3 let lazyload = (e) => {
4 for (let i = n; i < images.length; i++) {
5 const imageTop = images[i].getBoundingClientRect().top
6 if (imageTop < window.innerHeight) {
7 images[i].setAttribute('src', images[i].getAttribute('data-src'))
8 n = i + 1
9 }
10 }
11 console.log('scroll触发');
12 }`
3.两种距离判断方法的比较示意图
为了更好地帮助大家理解这个判断的区别,我画了一个示意图

为什么要优化懒加载
- 在前面的懒加载函数中,我们写了一个
log
- 好,现在我们去控制台看一下输出
scroll
多次监听事件多次触发,这是我们不愿意看到的
懒加载的两种优化方式
- 懒加载的优化本质上就是减少监听事件的调用
节流
- 在上一篇文章里面提到了节流,我们直接拿节流函数过来用
1`function throttle (fn, delay) {
2 let pre = 0
3 let timer
4 return function () {
5 if (!pre) pre = new Date()
6 let now = new Date()
7 let context = this
8 let args = arguments
9 let remainTime = delay - (now - pre)
10 if (now - pre > delay) {
11 fn.apply(context, args)
12 pre = now
13 } else {
14 if (timer) return
15 timer = setTimeout(() => {
16 fn.apply(context, args)
17 pre = now
18 timer = null
19 }, remainTime)
20 }
21 }
22 }
23 window.onscroll = throttle(lazyload, 1000)`
24
25
- 我们看控制台可以明显看到调用次数减少了
- 虽然完成了所有的懒加载后,但是我们
仍然可以触发事件
,所以才有了第二种优化方式

IntersectionObserver(第三种实现方法,同时是第二种优化方式)
IntersectionObserver
是什么?- 允许你追踪目标元素与其祖先元素或视窗的交叉状态。此外,尽管只有一部分元素出现在视窗中,哪怕只有一像素,也可以选择触发回调函数。
- 所以它完美契合了懒加载!
- 我们从代码中来学习它的用法吧!
1`const images = document.querySelectorAll('img')
2 const callback = (entries) => {
3 entries.forEach(entry => {
4 console.log(entry)
5 if (entry.isIntersecting) { // 监听到出现
6 const image = entry.target // 获取目标
7 image.setAttribute('src', image.getAttribute('data-src'))
8 observer.unobserve(image) // 取消监听
9 console.log('触发');
10 }
11 })
12 }
13 const observer = new IntersectionObserver(callback)
14 images.forEach(image => {
15 observer.observe(image)
16 })`
17
18
- 函数中
log
entry
,我们主要是康isIntersecting
属性来判断是否出现
- 通过这个优化方式,几张图片就会触发几次,不会多次触发!
observer
会监听交叉状态,即出现和消失,出现交叉状态后会去调用new
的时候传入的callback
回调函数observer.observe
添加交叉监听observer.unobserve
取消交叉监听- 上面这两个api基本够用了
- 给每张图片添加
observe
callback
里面判断交叉出现,就给src
赋值然后unobserve
最后,我们看一下优化后懒加载能提高多少性能
- 从第一次渲染加载
5张
图片变成了加载2张
图片 - 请求的总耗时从
7s
降到了6s
- 从我自己实验打开,还是明显感觉到要快一些了的(当然是在
3G慢速模式
下)
总结
- 1.前两种方法存在多次触发问题,所以我们可以使用节流来优化
- 2.第三种方法自己就考虑的优化(所以第三种方法也就是第二种优化方式)
- 3.咋一看,第三种方法是可以取代前两种方法的
- 但是,有的浏览器不兼容第三种方法的时候,我们就只能老老实实使用前两种方法并节流优化了
个人笔记记录 2021 ~ 2025