性能优化——图片懒加载

本文最后更新于:8 个月前

图片懒加载,关键在于如何判断图片是否出现在可视范围,本文介绍了两种实现方法:第一种,window.onscroll监听事件+节流+坐标对比;第二种,使用Intersection Observer接口

性能优化——图片懒加载

图片懒加载,简单来说,就是还没有出现在用户可见范围内的图像资源不加载

一、实现原理

<img>标签本身有一个loading属性来决定是否执行懒加载,但是实测中发现该属性在Edge浏览器中并不起作用,所以需要通过脚本的方式去实现。

懒加载的实现方式,通过判断<img>标签所处的坐标是否出现在了网页视口,将<img>标签的src值替换为存放在data-src属性里的真正图像链接

判断<img>标签是否出现在可视范围有两种实现方式:

  1. 监听window.onscroll事件,在回调函数中去对比每个<img>标签的坐标和窗口尺寸
  2. 使用Intersection Observer这个接口,判断<img>标签是否出现在可视范围



二、前置知识

1、浏览器屏幕坐标系

image-20221103101645081

存在两种坐标系

  • 相对于窗口window,从窗口的(顶部,左部)开始计算,坐标变量为**clientXclinetY**
  • 相对于文档document,从文档根的(顶部,左部)开始,坐标变量为**pageXpageY**

关系:pageX = clientX + scrollLeftpageY = clientY + scrollTop

其中scrollTop/scrollLeft代表页面节点与文档根(顶部,左部)的滚动距离

1
2
3
4
// 通过以下3种方式都可以获取到scrollTop(scrollLeft同理)
window.pageYOffset
document.body.scrollTop
document.documentElement.scrollTop



2、getBoundingClientRect()

该方法返回DOM节点的矩形盒子的属性对象,该矩形将 elem 作为内建 DOMRect 类的对象。这个对象有许多属性,

1
2
3
4
5
6
7
8
9
10
DOMRect {
x: 236.5, // 元素盒子左边界 与 窗口左边界的距离
y: 900, // 元素盒子上边界 与 窗口上边界的距离(可视窗口,不包括浏览器的工具栏等),负值表示位于窗口之上
width: 300, // 元素盒子宽度
height: 300, // 元素盒子高度
top: 900// 同 y
bottom: 1200, // top + height
left: 236.5, // 同 x
right: 536.5, // left + width
}

y属性的值 小于 window.innerHeight(可视窗口的高度)时,意味着元素进入了可视范围内



3、获取窗口尺寸

窗口有两种定义,一种是当前窗口的实际大小(可能因为被缩小而变化),另外一种是屏幕的大小(固定值)

第一种,获取当前窗口实际大小,使用window对象

1
2
3
4
5
6
7
8
9
// 窗口高度(后面2种适用于老版浏览器)
window.innerHeight
document.body.clientHeight
document.documentElement.clientHeight

// 窗口宽度
window.innerWidth
document.body.clientWidth
document.documentElement.clientWidth

第二种,获取屏幕固定宽高

1
2
3
4
5
// 屏幕宽度(屏幕是硬件设备,宽高都是固定的)
screen.width

// 屏幕高度
screen.height



4、Intersection Observer

该接口提供了一种异步观察目标元素与其祖先元素或顶级文档视窗 (viewport) 交叉状态的方法

该接口可以方便的检测元素的可视状态,应用场景广泛:

  • 当页面滚动时,懒加载图片或其他内容。
  • 实现“可无限滚动”网站,也就是当用户滚动网页时直接加载更多内容,无需翻页。
  • 对某些元素进行埋点曝光
  • 滚动到相应区域来执行相应动画或其他任务。

构造器

IntersectionObserver(),接受一个回调函数

1
2
3
4
5
const observer = new IntersectionObserver((entries)=>{
console.log(entries)
})
let hList = document.querySelectorAll('h3')
observer.observe(hList[0])

输出结果如下:

image-20221103130154181

可以看到enries是一个IntersectionObserverEntry对象数组,该对象的属性如上,其中isIntersecting是一个Boolean值,表示被观察的节点是否与文档视窗交叉(即有没有出现在可见窗口中)


属性 说明
IntersectionObserver.root 所监听对象的具体祖先元素 (element)。如果未传入值或值为null,则默认使用顶级文档的视窗
方法 说明
disconnect() 使IntersectionObserver对象停止监听工作
observe() 开始监听一个目标元素
unobserve() 停止监听特定目标元素

用法示例:

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
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<style>
h3 {
margin: 200px;
}
</style>
</head>
<body>
<div>
<h3>测试文本1</h3>
<h3>测试文本2</h3>
<h3>测试文本3</h3>
<h3>测试文本4</h3>
<h3>测试文本5</h3>
</div>
<script>
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
let node = entry.target
console.log(node.textContent + ' 出现在了窗口中')
observer.unobserve(node)
}
})
})
let hList = document.querySelectorAll('h3')
hList.forEach(h => {
observer.observe(h)
})
</script>
</body>
</html>

image-20221103130952810



三、动手实践

1.方式一:监听onscroll +节流

第一种方式:监听window.onscroll事件,在回调中判断图片是否出现在窗口可视范围,使用节流优化监听事件

完整文件源码:study/img_lazy_load_1.html at master · Qiuzcc/study · GitHub

核心代码:

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
<!DOCTYPE html>
<html lang="en">
<body>
<img src="https://qiuzcc-typora-images.oss-cn-shenzhen.aliyuncs.com/images/empty_box-Large.jpg"
data-src="https://qiuzcc-typora-images.oss-cn-shenzhen.aliyuncs.com/images/IMG_3995.JPG" alt="">

<script>
//节流函数,定时器版本,间隔interval时间执行一次fn函数,触发事件那一刻不执行,事件结束后还会执行一次
function throttle(fn, interval){...}
let imgs = document.getElementsByTagName('img')
let curLoadImgIndex = 0
function imgLazyLoad() {
for (let i = curLoadImgIndex; i < imgs.length; i++) {
let img = imgs[i]
if (img.getBoundingClientRect().top <= window.innerHeight) {
img.src = img.dataset.src
img.dataset.src = null
curLoadImgIndex = i + 1
}
}
}

// 监听滚动事件 + 节流(页面滚动过程中,每0.25秒执行一次回调函数)
window.onscroll = throttle(imgLazyLoad, 250)

// 立即执行,保证首屏的图片能够正确被加载
imgLazyLoad()
</script>
</body>
</html>



2.方式二:IntersectionObserver

完整文件代码:study/img_lazy_load_2.html at master · Qiuzcc/study · GitHub

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
const imgs = Array.from(document.getElementsByTagName('img'))
const imgLazyLoad = (imgs) => {
imgs.forEach(item => {
if (item.isIntersecting) {
const img = item.target
img.src = img.dataset.src
img.dataset.src = null
observer.unobserve(img)
}
})
}
const observer = new IntersectionObserver(imgLazyLoad)
imgs.forEach(img => { observer.observe(img) })
</script>

IntersectionObserver允许你追踪目标元素与其祖先元素或视窗的交叉状态

通过isIntersecting属性来判断是否出现

  • observer.observe添加交叉监听,给每个img添加监听
  • observer.unobserve取消交叉监听,img出现时给src赋值,并取消监听



参考链接

【译】浏览器的坐标系统 · YAPN (gitbooks.io)

坐标 (javascript.info)

Intersection Observer - Web API 接口参考 | MDN (mozilla.org)


性能优化——图片懒加载
http://timegogo.top/2022/11/02/前端工程化/性能优化:图片懒加载/
作者
丘智聪
发布于
2022年11月2日
更新于
2023年7月16日
许可协议