July 6, 2023

渲染性能优化:懒加载-异步解码

[TOC]

懒加载

懒加载是一种网页性能优化的常见方式,它能极大的提升用户体验。到今天,一个网站里面可能会有几百张甚至几千张上万张图片和其他资源,如果每次进入页面都需要请求页面上的所有的资源,会较大的影响用户体验,对用户的带宽也是一种极大的损耗。

所以,懒加载的意义即是,当页面未滚动到相应区域,该区域内的资源(网络请求)不会被加载。反之,当页面滚动到相应区域,相关资源的请求才会被发起。

之前,图片的懒加载通常都是使用 JavaScript 方案进行:

先给页面的图片设置一个占位符,不要把图片真实地址放在src中,真正的路径存储在一个属性中,要用的时候再取出来,页面加载完成后,判断图片是否在用户视野,如果在,就把值取出放入src属性;

如果 offsetTop - scrollTop < clientHeight,说明图片出现在可视区域

而今天,我们在懒加载实现上,有了更多不一样的选择。下面会介绍两种方式并进行测验:

1 使用 content-visibility 实现延迟渲染

1.1 What

content-visibility: auto

其控制一个元素,直到需要它的时候才会渲染

利用 content-visibility ,我们可以实现如果该元素当前不在屏幕上,则不会渲染其后代元素。

1.2 Demo

如果有如下页面结构,我使用 items 数组存放15个照片,都是通过网络请求拿到的,模板中进行渲染

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
<div v-for="item in items" :key="item">
<div class="container-box">
<p>-------------{{ item.id }}-------------</p>
<img :src="item.src" alt=""/>
</div>
</div>
<script>
export default {
data() {
return {
items: [{
id: 0,
src: "http://b.hiphotos.baidu.com/image/pic/item/9d82d158ccbf6c81b94575cfb93eb13533fa40a2.jpg"
},{
id: 1,
src: "https://gtd.alicdn.com/sns_logo/i3/TB1wnBTKFXXXXcQXXXXSutbFXXX.jpg_240x240xz.jpg"
}, {
id: 2,
src: "//www.baidu.com/img/flexible/logo/pc/result.png"
}, {
id: 3,
src: "http://e.hiphotos.baidu.com/image/pic/item/a1ec08fa513d2697e542494057fbb2fb4316d81e.jpg"
}, {
id: 4,
src: "http://c.hiphotos.baidu.com/image/pic/item/30adcbef76094b36de8a2fe5a1cc7cd98d109d99.jpg"
}, {
id: 5,
src: "http://h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg"
}, {
id: 6,
src: "http://g.hiphotos.baidu.com/image/pic/item/55e736d12f2eb938d5277fd5d0628535e5dd6f4a.jpg"
}, {
id: 7,
src: "http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg"
}, {
id: 8,
src: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg"
}, {
id: 9,
src: "http://e.hiphotos.baidu.com/image/pic/item/4bed2e738bd4b31c1badd5a685d6277f9e2ff81e.jpg"
}, {
id: 10,
src: "http://g.hiphotos.baidu.com/image/pic/item/0d338744ebf81a4c87a3add4d52a6059252da61e.jpg"
}, {
id: 11,
src: "http://a.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee5080c8142ff5e0fe99257e19.jpg"
}, {
id: 12,
src: "http://f.hiphotos.baidu.com/image/pic/item/4034970a304e251f503521f5a586c9177e3e53f9.jpg"
}, {
id: 13,
src: "http://b.hiphotos.baidu.com/image/pic/item/279759ee3d6d55fbb3586c0168224f4a20a4dd7e.jpg"
}, {
id: 14,
src: "http://a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg"
}]
}
}
}
</script>

页面看起来是这样的:

只需要给需要延迟(实时)渲染的元素,设置简单的 CSS 样式:

1
2
3
.container-box {
content-visibility: auto;
}

就可以实现延迟加载了

1.3 可访问性

全文检索是一个重要的功能

如果说可视区外的内容未被渲染,那是否会影响用户进行全文检索呢?

在页面初始化后,我全局 ctrl + F,搜索 7 ,全局查找的时候,可以找到当前未被渲染的元素内的内容

因此,即便存在设置了 content-visibility: auto 的未被渲染的元素,但是它并不会影响全局的搜索功能

1.4 But

文章还没到一半,当然不会就这么结束了

因为有2个缺点

1.3.1 利用 contain-intrinsic-size 解决滚动条抖动问题

当我们使用 content-visibility: auto,那么可视区外的元素高度通常就为 0,当滚动条移动,元素展示,所以会出现抖动抽搐。

如果能准确知道设置了元素在渲染状态下的高度,就可以避免抖动现象了。

因此我为元素加上 contain-intrinsic-size 属性:

1
2
3
4
.container-box {
contain-intrinsic-size: 320px;
content-visibility: auto;
}

效果:

所以,content-visibility: auto 无法完全替代图片懒加载,设置了 content-visibility: auto 的确实现了延迟渲染,但是其中的静态资源仍旧会在页面初始化的时候被全部加载。因此,它更适合于虚拟列表渲染的情景。

2 懒加载+异步解码

2.1 懒加载 loading=”lazy”

HTML5 新增了一个 loading 属性。此属性可以添加到 < img > 元素中,也可以添加到 < iframe > 元素中。

属性的值为 loading=”lazy” 会告诉浏览器,如果图像位于可视区时,则立即加载图像,并在用户滚动到它们附近时获取其他图像。

loading=”lazy” 到(2023-07-08)的兼容性,还是非常不错的:

也可以通过以下方法检测浏览器是否支持 loading="lazy"
1
var isSupportLoading = 'loading' in document.createElement('img');

2.2 异步解码 decoding=”async”

HTMLImageElement 接口的 decoding 属性用于告诉浏览器使用何种方式解析图像数据。

浏览器在进行图片渲染展示的过程中,是需要对图片文件进行解码的,这一个过程快慢与图片格式有关。

而如果我们不希望图片的渲染解码影响页面的其他内容的展示,可以使用 async 选项

这样,浏览器便会异步解码图像,加快显示其他内容,优化页面渲染性能。

同样的,我们来看看到(2023-07-08),decoding=”async” 的兼容性,整体还是非常不错的,作为渐进增强方案使用,是非常好的选择。

2.3 Demo

和上面的Demo差不多,准备14张图片的页面,每个图片url大小不一

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
<template>
<div class="container">
<!-- loading="lazy" 懒加载 -->
<div v-for="item in items" :key="item">
<div class="container-box2">
<p>-------------{{ item.id }}-------------</p>
<img :src="item.src" alt="" loading="lazy" decoding="async"/>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [{
id: 0,
src: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg"
},{
id: 1,
src: "https://gtd.alicdn.com/sns_logo/i3/TB1wnBTKFXXXXcQXXXXSutbFXXX.jpg_240x240xz.jpg"
}, {
id: 2,
src: "//www.baidu.com/img/flexible/logo/pc/result.png"
}, {
id: 3,
src: "http://e.hiphotos.baidu.com/image/pic/item/a1ec08fa513d2697e542494057fbb2fb4316d81e.jpg"
}, {
id: 4,
src: "http://c.hiphotos.baidu.com/image/pic/item/30adcbef76094b36de8a2fe5a1cc7cd98d109d99.jpg"
}, {
id: 5,
src: "http://h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg"
}, {
id: 6,
src: "http://g.hiphotos.baidu.com/image/pic/item/55e736d12f2eb938d5277fd5d0628535e5dd6f4a.jpg"
}, {
id: 7,
src: "http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg"
}, {
id: 8,
src: "http://b.hiphotos.baidu.com/image/pic/item/9d82d158ccbf6c81b94575cfb93eb13533fa40a2.jpg"
}, {
id: 9,
src: "http://e.hiphotos.baidu.com/image/pic/item/4bed2e738bd4b31c1badd5a685d6277f9e2ff81e.jpg"
}, {
id: 10,
src: "http://g.hiphotos.baidu.com/image/pic/item/0d338744ebf81a4c87a3add4d52a6059252da61e.jpg"
}, {
id: 11,
src: "http://a.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee5080c8142ff5e0fe99257e19.jpg"
}, {
id: 12,
src: "http://f.hiphotos.baidu.com/image/pic/item/4034970a304e251f503521f5a586c9177e3e53f9.jpg"
}, {
id: 13,
src: "http://b.hiphotos.baidu.com/image/pic/item/279759ee3d6d55fbb3586c0168224f4a20a4dd7e.jpg"
}, {
id: 14,
src: "http://a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg"
}]
}
}
}
</script>

由于是纯图片页面,需要给给图片设置默认宽高。

否则初次刷新时,元素的宽高都是 0,会导致所有元素都在可视区内。

1
2
3
4
5
6
7
8
<style>
img {
margin: 8px;
width: 202px;
height: 166px;
object-fit: cover; /* 居中自动剪裁图片 */
}
</style>

效果:

可以看到,初次加载时,只会请求当前页面的资源,随着可视区域变化,再进行资源请求

2.4 性能测试

为了验证性能,在上面的Demo基础上,进行对比测试。

首先是没有添加懒加载的元素

1
<img :src="item.src" alt=""/>

可以看到,初次页面加载时会请求所有资源,页面 Load 事件完成的时间竟然有 14.96 s

现在我们为他加上 loading=”lazy” 与 decoding=async”

1
<img :src="item.src" alt="" loading="lazy" decoding="async" />

首次进入页面仅请求显示在屏幕上的图片,页面 Load 事件完成的时间为 597 ms

当页面滚动时,显示的图片才会继续发送请求

3 总结一下

About this Post

This post is written by Duan WeiCheng, licensed under CC BY-NC 4.0.