前言
前端谈到站点图片优化的策略,一定绕不过懒加载。比如开发Vue应用首选的方案大概会是VueLazyLoad, 但大部分人在做懒加载技术方案选型的时候可能会忽略它们会带来的一些副作用。
VueLazyload 的基本原理
- 用户提供
src
,loading
,error
三张图片,分别表示要加载图片,图片加载中显示的占位图,图片加载错误时显示的占位图 - VueLazyload 在初始化时会显示
loading
占位图,然后在判断图片元素进入视口后,通过 jsnew Image()
创建一个图片实例异步加载src
指定的图片 - 图片加载成功显示
src
指定的图片,图片加载失败后显示error
指定的图片
浏览器资源下载优先级
谈VueLazyload
的问题前,先简单介绍一下资源获取优先级(priority)
的概念。
资源获取优先级(priority)
,是浏览器在加载网页时,决定优先下载哪些资源、延后加载哪些资源的机制。
浏览器在下载资源图片、脚本、css等资源时,会为这些资源分配一个提取priority
,以便以最佳顺序下载它们。 比如图片资源下载时一般会被分配为Low
优先级,但是位于视口内的图片优先级可能会被提升为High
,被提升为High
后浏览器会更早的去下载这些图片资源,从而更快发生 LCP。
VueLazyload带来的性能问题
结合上面的分析,不难发现VueLazyload
可能出现的问题,因为所有(即使在视口中)的图片,都是通过new Image()
的方式获取,这样就会导致图片的优先级只能是Low
,浏览器可能会优先下载优先级较高的资源,推迟下载图片资源,因为图片下载时间点被推迟,可能让用户觉得页面加载慢,如果是轮播图这种视口区域占比比较大的元素也会导致核心性能指标LCP
的时间变大。
一个简单的例子模拟一下VueLazyload
加载图片
html
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test</title>
</head>
<body>
<div>
<img id="myImg" src="./test/loading.png" />
</div>
</body>
<script>
const img = new Image();
img.onload = () => {
const imdDom = document.querySelector('#myImg');
imdDom.src = img.src;
};
img.src = './test/test.jpg';
// 插入5个css和脚本
for (let i = 1; i <= 5; i++) {
const link = document.createElement('link');
const script = document.createElement('script');
link.rel = 'stylesheet';
link.href = `./test/${i}.css`;
document.head.appendChild(link);
script.src = `./test/${i}.js`;
document.head.appendChild(script);
}
</script>
</html>
通过谷歌的Network
面板观察
PS: 为了能够看到测试效果,把网络调整成了Slow 4G
发现图片的下载优先级为
Low
,所有css资源的下载优先级为Highest
。
通过图片资源的
Timing
面板也看到,图片请求在请求队列中等待了1.11s,这就导致了用户可能会更晚的看到图片资源,带来不好的用户体验。
LCP
性能指标也受影响。
尝试现代的懒加载方案
针对首屏视口的图片不要用懒加载,而是直接把图片地址写在src
属性里面,如果不能确定图片首屏的位置,建议在图片标签里面加上loading="lazy"
实现延迟加载,目前该方案兼容性也很不错。MDN文档传送门。
接下来用这种方案改写上面的例子
html
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test</title>
<style>
#myImg {
width: 100%;
height: auto;
background-image: url('./test/loading.png');
}
</style>
</head>
<body>
<div>
<img id="myImg" src="./test/test.jpg" loading="lazy" />
</div>
</body>
<script>
for (let i = 1; i <= 5; i++) {
const link = document.createElement('link');
const script = document.createElement('script');
link.rel = 'stylesheet';
link.href = `./test/${i}.css`;
document.head.appendChild(link);
script.src = `./test/${i}.js`;
document.head.appendChild(script);
}
</script>
</html>
把loading
图片设置成图片元素的背景,看一下效果。
发现图片的下载优先级由
Low
提升成了High
图片请求在请求队列中等待了840ms ,比之前大概提升了0.3s
LCP
性能指标也得到了提升。
接下来试试直接把loading="lazy"
去掉再试试。
请求的排毒时间变为了14.3ms,基本上没有了排队时间。
LCP
性能指标更是得到大幅度提升。
总结
- 首屏加载的大图避免使用任何懒加载方案,从而更快发生
LCP
。 - 对于不能确定是否会在首屏显示的大图,可以考虑使用
loading="lazy"
的懒加载方案