图片延迟加载
什么是延迟加载
首先来想象一个场景,当浏览一个内容丰富的网站时,比如电商的商品列表页、主流视频网站的节目列表等,由于屏幕尺寸的限制,每次只能查看到视窗中的那部分内容,而要浏览完页面所包含的全部信息,就需要滚动页面,让屏幕视窗依次展示出整个页面的所有局部内容。
显而易见,对于首屏之外的内容,特别是图片和视频,一方面由于资源文件很大,若是全部加载完,既费时又费力,还容易阻塞渲染引起卡顿;另一方面,就算加载完成,用户也不一定会滚动屏幕浏览到全部页面内容,如果首屏内容没能吸引住用户,那么很可能整个页面就将遭到关闭。
既然如此,本着节约不浪费的原则,在首次打开网站时,应尽量只加载首屏内容所包含的资源,而首屏之外涉及的图片或视频,可以等到用户滚动视窗浏览时再去加载。
以上就是延迟加载优化策略的产生逻辑,通过延迟加载“非关键”的图片及视频资源,使得页面内容更快地呈现在用户面前。这里的“非关键”资源指的就是首屏之外的图片或视频资源,相较于文本、脚本等其他资源来说,图片的资源大小不容小觑。
实现图片延迟加载
传统方式
传统的懒加载方式,其实就是通过监听滚动轴的变化,去请求出现在可视区域的图片。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载-传统方式</title>
<style>
.box {
width: 800px;
height: 400px;
border: 1px solid;
overflow:auto;
}
.placeholder{
height: 400px;
}
.img {
width: 100px;
height: 100px;
border: 1px solid;
display: block;
margin: 40px auto;
}
</style>
</head>
<body>
<div class="box">
<div class="placeholder">首屏占位</div>
<img class="img" src="" data-src="./imgs/01.webp" />
<img class="img" src="" data-src="./imgs/02.webp" />
<img class="img" src="" data-src="./imgs/03.webp" />
<img class="img" src="" data-src="./imgs/04.webp" />
<img class="img" src="" data-src="./imgs/05.webp" />
<img class="img" src="" data-src="./imgs/06.webp" />
</div>
<script>
// 获取容器元素及图片元素
const box = document.querySelector('.box')
const imgs = document.querySelectorAll('.img')
// 获取容器高度
const boxHeight = box.clientHeight
// 监听容器滚动事件
box.addEventListener('scroll', handleScroll)
// 滚动事件处理函数
function handleScroll () {
// 获取容器滚动距离
const scrollTop = box.scrollTop
// 遍历图片
imgs.forEach(img => {
// 判断图片是否出现在可视区域(设置 100px 缓冲区查看懒加载效果)
if (img.offsetTop < boxHeight + scrollTop - 100) {
// 将图片地址填充到 src 属性上
img.src = img.getAttribute('data-src')
}
})
}
</script>
</body>
</html>
也可以在此基础上进行更多优化,例如:
- 为监听滚动添加防抖处理
- 为图片设置默认图片
Intersection Observer 方式
对于传统的实现方式,Web API 提供了一个类似的 API:Intersection Observer 它又称为交叉观察者,是通过 IntersectionObserver
API 创建一个观察者实例。它提供了一种异步观察目标元素与祖先元素或视窗( viewport
)交叉状态的方法。祖先元素与视窗(viewport
)被称为根(root)。
可以通过配置options
对象中的 rootMargin
属性来建立缓冲区。
当一个 IntersectionObserver
实例被创建,它就会被配置为监听 root 中一段给定比例可见区域的监听器。可以通过 IntersectionObserver.observe()
方法监听指定的目标元素。
当目标元素距离进入监听区域,就会开始执行回调函数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载-传统方式</title>
<style>
.box {
width: 800px;
height: 400px;
border: 1px solid;
overflow:auto;
}
.placeholder{
height: 400px;
}
.img {
width: 100px;
height: 100px;
border: 1px solid;
display: block;
margin: 40px auto;
}
</style>
</head>
<body>
<div class="box">
<div class="placeholder">首屏占位</div>
<img class="img" src="" data-src="./imgs/01.webp" />
<img class="img" src="" data-src="./imgs/02.webp" />
<img class="img" src="" data-src="./imgs/03.webp" />
<img class="img" src="" data-src="./imgs/04.webp" />
<img class="img" src="" data-src="./imgs/05.webp" />
<img class="img" src="" data-src="./imgs/06.webp" />
</div>
<script>
// 获取图片元素
const box = document.querySelector('.box')
const imgs = document.querySelectorAll('.img')
// 创建交叉观察者
const observer = new IntersectionObserver((entries) => {
// entries - 发生交叉的目标元素集合
entries.forEach(item => {
// 判断是否发生交叉
if (item.isIntersecting) {
// 替换图片地址
item.target.src = item.target.getAttribute('data-src')
// 取消监听此目标元素
observer.unobserve(item.target)
}
})
}, {
// 指定监听区域的容器元素,默认窗口可视区域
root: box,
// 设置监听矩形区域的偏移,例如在目标出露出边界底部 100px 的时候执行回调
// 必须携带像素单位或使用百分比
rootMargin: '0px 0px -100px 0px'
})
// 监听图片元素
imgs.forEach(img => {
observer.observe(img)
})
</script>
</body>
</html>
观察可知 rootMargin
的值与 CSS 中 margin 属性值类似,上述代码中在屏幕视窗下设置了一个高度为 100px
的反向缓冲区(正常应该在屏幕外就开始执行回调),这意味着当媒体元素距离露出视窗下边界超过 100px
时,回调函数就会执行开始资源的请求加载。
而对于使用监听滚动事件处理来实现延迟加载的传统实现方式,也可以通过 getBoundingClientRect
方法获取目标元素相对与视口的位置。
PS:使用 Intersection Observer 还可以用来实现文档阅读中的标题和导航联动。
原生的延迟加载支持
<img>
有个原生属性 loading
用于提供原生的懒加载策略。
<img src="xxx.png" loading="lazy" />
注意:这个属性还在实验阶段,使用时需注意兼容性。
CSS 类名方式
上面的方式都是对 <img>
元素进行懒加载优化,而有的图片是作为背景图使用的,就需要做些调整:
- 监听滚动的方式:JS 修改
backgroundImage
属性 - 原生
loading
方式:不支持背景图场景
建议的方式是通过切换 CSS 类名来实现懒加载:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载-传统方式</title>
<style>
.box {
width: 800px;
height: 400px;
border: 1px solid;
overflow:auto;
}
.placeholder{
height: 400px;
}
.img {
width: 100px;
height: 100px;
border: 1px solid;
display: block;
margin: 40px auto;
background: url() 0 0 no-repeat;
background-size: cover;
}
.img1.visible {
background-image: url(./imgs/01.webp);
}
.img2.visible {
background-image: url(./imgs/02.webp);
}
.img3.visible {
background-image: url(./imgs/03.webp);
}
.img4.visible {
background-image: url(./imgs/04.webp);
}
.img5.visible {
background-image: url(./imgs/05.webp);
}
.img6.visible {
background-image: url(./imgs/06.webp);
}
</style>
</head>
<body>
<div class="box">
<div class="placeholder">首屏占位</div>
<div class="img img1"></div>
<div class="img img2"></div>
<div class="img img3"></div>
<div class="img img4"></div>
<div class="img img5"></div>
<div class="img img6"></div>
</div>
<script>
// 获取图片元素
const box = document.querySelector('.box')
const imgs = document.querySelectorAll('.img')
// 创建交叉观察者
const observer = new IntersectionObserver((entries) => {
// entries - 发生交叉的目标元素集合
entries.forEach(item => {
// 判断是否发生交叉
if (item.isIntersecting) {
// 添加 CSS 类名
item.target.classList.add('visible')
observer.unobserve(item.target)
}
})
}, {
// 指定监听区域的容器元素,默认窗口可视区域
root: box,
// 设置监听矩形区域的偏移,例如在目标出露出边界底部 100px 的时候执行回调
// 必须携带像素单位或使用百分比
rootMargin: '0px 0px -100px 0px'
})
// 监听图片元素
imgs.forEach(img => {
observer.observe(img)
})
</script>
</body>
</html>
加载注意事项
对图像等资源的延迟加载,从理论上看必然会对性能产生重要的影响,但在实现过程中有许多细节需要注意,稍有差池都可能就会产生意想不到的结果。因此,总结以下几点注意事项。
资源占位
当延迟加载的媒体资源未渲染出来之前,应当在页面中使用相同尺寸的占位图像。如果不使用占位图,图像延迟显示出来后,尺寸更改可能会使页面布局出现移位。
这种现象不仅会对用户体验带来困惑,更严重的还会触发浏览器成本高昂的回流机制,进而增加系统资源开销造成卡顿。而用来占位的图像解决方案也有多种,十分简单的方式是使用一个与目标媒体资源长宽相同的纯色占位符,或者使用 Base64 图片,当然也可以采用 LQIP 或 SQIP 等方法。
LQIP 和 SQIP 是两种方式的占位符工具包:
- lqip:Low Quality Image Placholders,基于 base64 的低质量图像占位符
- 可以根据原始图片自动生成超小尺寸的模糊缩略图,转换为很小的 base64 字符串
- 可以获取指定图片的调色板数组
- sqip:SVG Quality Image Placeholders,基于 SVG 的 LQIP 技术
- 将指定图片转化为 SVG,并使用 SVGO 优化,以及添加指定数值的高斯模糊效果
- 相比 LQIP,SQIP 可以设置不同的大小
其实就是以最小的带宽消耗,告知用户此处将要展示一个媒体资源,可能由于资源尺寸较大还在加载。对于使用 <img>
标记的图像资源,应将用于占位的初始图像指给 src
属性,直到更新为所需的最终图像为止。而对于使用 <vidoe>
标记的视频资源,则应将占位图像指给 poster
属性,除此之外,最好可以在 <img>
和 <video>
标签上添加表示宽 width
和高 height
的属性,如此便可确保不会在占位符转化为最终媒体资源时,发生元素渲染大小的改变。
内容加载失败
在进行延迟加载过程中,可能会因为某种原因而造成媒体资源加载失败,进而导致错误的情况。比如用户访问某个网站后,保持浏览器该选项卡打开后长时间离开,等再返回继续浏览网页内容时,可能在此过程中网站已经进行了重新部署,原先访问的页面中包含的部分媒体资源由于哈希的版本控制发生更改,或者已被移除。那么用户滚动浏览页面,遇到延迟加载的媒体资源,可能就已经不可使用了。
虽然类似情况发生的概率不高,但考虑网站对用户的可用性,开发者也应当考虑好后备方案,以防止类似延迟加载可能遇到的失败。例如,图像资源可以采取如下方案进行规避:
const newImage = new Image()
newImage.src = "photo.jpg"
// 当发生故障时的处理措施
newImage.onerror = err => {
}
// 图像加载后的回调
newImage.onload = () => {
}
当图片资源未能按预期成功加载时,所采取的具体处理措施应当依据应用场景而定。比如,当请求的媒体资源无法加载时,可将使用的图像占位符替换为按钮,让用户单击以尝试重新加载所需的媒体资源,或者在占位符区域显示错误的提示信息。总之,在发生任何资源加载故障时,给予用户必要的通知提示,总好过直接让用户无奈地面对故障。
图像解码延迟
在前面《图片优化》章节介绍 JPEG 图像的编解码时,我们知道渐进式的 JPEG 会先呈现出一个低像素的图像版本,随后会慢慢呈现出原图的样貌。这是因为图像从被浏览器请求获取,再到最终完整呈现在屏幕上,需要经历一个解码的过程,图像的尺寸越大,所需要的解码时间就越长。如果在 JavaScript 中请求加载较大的图像文件,并把它直接放入 DOM 结构中后,那么将有可能占用浏览器的主进程,进而导致解码期间用户界面出现短暂的无响应。
为减少此类卡顿现象,可以采用 HTMLImageElement.decode() 方法进行异步图像解码后,再将其插入 DOM 结构中。但目前这种方式在跨浏览器场景下并不通用,同时也会复杂化原本对于媒体资源延迟加载的处理逻辑,所以在使用中应进行必要的可用性检查。下面是一个使用 HTMLImageElement.decode()
函数来实现异步解码的示例:
<button id="load-image">加载图像</button>
<div id="image-container"></div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const loadButton = document.getElementById('load-image')
const imageContainer = document.getElementById('image-container')
const newImage = new Image()
newImage.src = 'https://xx.cdn/very-big-photo.jpg'
loadButton.addEventListener('click', function () {
if ('decode' in newImage) {
// decode() 返回一个 Promise
newImage.decode().then(function () {
imageContainer.appendChild(newImage)
})
} else {
// 正常图像加载方式
imageContainer.appendChild(newImage)
}
})
})
</script>
需要说明的是,如果网站所包含的大部分图像尺寸都很小,那么使用这种方式的帮助并不会很大,同时还会增加代码的复杂性。但可以肯定的是这么做会减少延迟加载大型图像文件所带来的卡顿。
JavaScript 是否可用
在通常情况下,我们都会假定 JavaScript 始终可用,但在一些异常不可用的情况下,开发者应当做好适配,不能始终在延迟加载的图像位置上展示占位符。可以考虑使用 <noscript>
标记,在 JavaScript 不可用时提供图像的真实展示:
<!-- 使用延迟加载的图像文件标签 -->
<img class="lazy" src="placeholder-image.jpg" data-src="image-to-lazy-load.jpg" alt="I`m an image!" />
<!-- 当 JavaScript 不可用时,原生展示目标图像 -->
<noscript>
<img src="image-to-lazy-load.jpg" alt="I`m an image!"
</noscript>
如果上述代码同时存在,当 JavaScript 不可用时,页面中会同时展示图像占位符和 <noscript>
中包含的图像,为此我们可以给 <html>
标签添加一个 no-js
类:
<html class="no-js">
在由 <link>
标签请求 CSS 文件之前,在 <head>
标签结构中放置一段内联脚本,当 JavaScript可用时,用于移除 no-js
类:
<script>document.documentElement.classList.remove("no-js");</script>
以及添加必要的 CSS 样式,使得在 JavaScript 不可用时屏蔽包含 .lazy
类元素的显示:
.no-js .lazy {
display: none;
}
当然这样并不会阻止占位符图像的加载,只是让占位符图像在 JavaScript 不可用时不可见,但其体验效果会比让用户只看到占位符图像和没有意义的图像内容要好许多。
参考
视频加载
与延迟加载图像资源类似,通过 <video>
引入的视频资源也可进行延迟加载,但通常都会根据需求场景进行具体的处理。
不需要自动播放
对于一些需要由用户自己播放的视频,最好指定 <video>
标签的 preload
属性为 none
,这样浏览器就不会预加载任何视频数据。
为了占用空间,可以使用 poster
属性为 <video>
占位,实现如下:
<video controls preload="none" poster="placeholder.jpg">
<source src="main.webm" type="vidoe/webm">
<source src="main.mp4" type="vidoe/mp4">
</video>
视频代替 GIP 动画
在前面的介绍 GIF 图片的章节中建议,将内容较长的 GIF 用视频代替。
GIF 动画相对于视频具有三个附加的特性:没有音轨、连续循环播放、加载完自动播放,替换成视频后类似:
<video controls autoplay loop muted playsinline>
<source src="main.webm" type="vidoe/webm">
<source src="main.mp4" type="vidoe/mp4">
</video>
- autoplay 自动播放
- muted 静音
- loop 循环播放
- playsinline 用于在 iOS 系统中自动播放
视频懒加载
视频也可以像图片一样实现基于 Intersection Observer API 的懒加载机制。
<video class="lazy" controls autoplay poster="placeholder.jpg">
<source src="" data-src="main.webm" type="vidoe/webm">
<source src="" data-src="main.mp4" type="vidoe/mp4">
</video>
<script>
document.addEventListener("DOMContentLoaded", () => {
// NodeList 不是数组,需要使用 call 调用数组的实例方法
const lazyVideos = [].slice.call(document.querySelectorAll("video.lazy"));
if("IntersectionObserver" in window) {
const lazyVideoObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((video) => {
if(video.isIntersecting) {
// 遍历 source 元素,用 data-src 替换 src
for(const source in video.target.children) {
const videoSrc = video.target.children[source];
if (typeof videoSrc.tagName === "string" && videoSrc.tagName === " source") {
videoSrc.src = videoSrc.dataset.src;
}
}
// 触发加载
video.target.load();
// 移除类名
video.target.classList.remove("lazy");
// 取消监听
lazyVideoObserver.unobserve(video.target);
}
})
})
lazyVideos.forEach((lazyVideo) => {
lazyVideoObserver.observe(lazyVideo);
});
}
})
</script>
视频懒懒加载与图片类似都是替换资源地址,不同的是,需要额外显式调用 <video>
元素的 load
方法来触发加载,然后视频才会根据 autoplay
属性开始进行自动播放,这样就能使用低于 GIF 动画的流量消耗,进行资源的延迟加载。
参考
字体优化
字体的优化主要是这几个方面:
- 转化为 woff2 格式的字体
- font-spider 裁剪字体
- 使用 Font Face Observer 配置加载字体策略
- 其他:缓存、CDN、压缩等
参考:
路由懒加载
路由懒加载也可以叫做路由组件懒加载,通常是通过 import()
实现在执行到这段代码的时候才开始加载模块,也就是按需加载。
配置了代码分割的 Webpack 编译打包后,它会将通过 import()
导入的文件单独打包成一个 boudle 文件,这样会大大减少加载首屏页面所需的初始化文件,从而提高首屏渲染速度。
例如 vue 的路由:
{
path: '*',
name: '404',
component: () => import(/* webpackChunkName: "404" */ '@/components/404.vue'),
},
资源优先级
浏览器向网络请求到的所有数据,并非每个字节都具有相同的优先级或重要性。所以浏览器通常都会采取启发式算法,对所要加载的内容先进行推测,将相对重要的信息优先呈现给用户,比如浏览器一般会先加载 CSS 文件,然后再去加载 JavaScript 脚本和图像文件。
但即便如此,也无法保证启发式算法在任何情况下都是准确有效的,可能会因为获取的信息不完备,而做出错误的判断。本节来探讨如何影响浏览器对资源加载的优先级。
优先级
浏览器基于自身的启发式算法,会对资源的重要性进行判断来划分优先级,通常从低到高分为: Lowest、Low、High、Highest 等。
比如,在 <head>
标签中,CSS 文件通常具有最高的优先级 Highest,其次是 <script>
标签所请求的脚本文件,但当 <script>
标签带有 defer
或 async
的异步属性时,其优先级又会降为 Low。
我们可以通过 Chrome 的开发者工具,在 Network 页签下找到浏览器对资源进行的优先级划分,如下图所示。
我们可以通过该工具,去了解浏览器为不同资源分配的优先级情况,细微的差别都可能导致类似的资源具有不同的优先级,比如首屏渲染中图像的优先级会高于屏幕视窗外的图像的优先级。
本节不会详细探讨 Chrome 如何为当前资源分配优先级,可通过搜索“浏览器加载优先级”等关键字自行了解。
本节对性能优化实战而言,会更加关注:当发现资源默认被分配的优先级不是我们想要的情况时,该如何更改优先级。
接下来介绍三种不同的解决方案:首先是前面章节提到过的预加载,当资源对用户来说至关重要却又被分配了过低的优先级时,就可以尝试让其进行预加载(preload
)或预连接(preconnet
);如果仅需要浏览器处理完一些任务后,再去提取某些资源,可尝试使用预提取(prefetch
)。
预加载
使用 <link rel="preload">
标签告诉浏览器当前所指定的资源,应该拥有更高的优先级,例如:
<link rel="preload" as="style" href="a.css">
<link rel="preload" as="script" href="b.js">
这里通过 as
属性告知浏览器所要加载的资源类型,该属性值所指定的资源类型应当与要加载的资源相匹配,否则浏览器是不会预加载该资源的。
在这里需要注意的是,<link rel="preload">
会强制浏览器进行预加载,它与其他对资源的提示不同,浏览器对此是必须执行而非可选的。因此,在使用时应尽量仔细测试,以确保使用该指令时不会提取不需要的内容或重复提取内容。
如果预加载指定的资源在 3s 内未被当前页面使用,则浏览器会在开发者工具的控制台中进行警告提示,该警告务必要处理,如下图所示:
示例1:字体的使用
通常字体文件都位于页面加载的若干 CSS 文件的末尾,但考虑为了减少用户等待文本内容的加载时间,以及避免系统字体与偏好字体发生冲突,就必须提前获取字体。因此我们可以使用 <link rel="preload">
来让浏览器立即获取所需的字体文件:
<link rel="preload" as="font" crossorigin="crossorigin" type="font/woff2" href="myfont.woff2">
这里的
crossorigin
属性非常重要,因为@font-face
中加载字体默认发起的是跨域请求。
示例2:关键路径渲染
在前面《前端页面的生命周期》中提到过关键渲染路径,其中涉及首次渲染之前必须加载的资源(比如 CSS 和 JavaScript 等),这些资源对首屏页面渲染来说是非常重要的。
以前通常建议的做法是把这些资源内联到 HTML 中,但对服务器端渲染或对页面而言,这样做很容易导致带宽浪费,而且若代码更改使内联页面无效,无疑会增加版本控制的难度。
所以使用 <link rel="preload">
对单个文件进行预加载,除了能很快地请求资源,还能尽量利用缓存。其唯一的缺点是可能会在浏览器和服务器之间发生额外的往返请求,因为浏览器需要加载解析 HTML 后,才会知道后续的资源请求情况。
其解决方式可以利用 HTTP 2 的推送,即在发送 HTML 的相同连接请求上附加一些资源请求,如此便可取消浏览器解析 HTML 到开始下载资源之间的间歇时间。
但对于 HTTP 2 推送的使用需要谨慎,因为控制了带宽使用量,留给浏览器自我决策的空间便会很小,可能不会检索已经缓存了的资源文件。
预连接
参考:用于预连接的 rel
通常会考虑添加
preconnect
或dns-prefetch
来提示建立与重要第三方源的早期连接。
通常在速度较慢的网络环境中建立连接会非常耗时,如果建立安全连接将更加耗时。其原因是整个过程会涉及 DNS 查询、重定向和与目标服务器之间建立连接的多次握手,所以若能提前完成上述这些功能,则会给用户带来更加流畅的浏览体验,同时由于建立连接的大部分时间消耗是等待而非数据交换,这样也能有效地优化带宽的使用情况。解决方案就是所谓的预连接:
<link rel="preconnect" href="https://example.com"
通过 <link rel="preconnect">
标签指令,告知浏览器当前页面将与站点建立连接,希望尽快启动该过程。
虽然这么做的成本较低,但会消耗宝贵的 CPU 时间,特别是在建立 HTTPS 安全连接时。如果建立好连接后的 10s 内,未能及时使用连接,那么浏览器关闭该连接后,之前为建立连接所消耗的资源就相当于完全被浪费掉了。
另外,还有一种与预连接相关的类型 <link rel="dns-prefetch">
,也就是常说的 DNS 预解析,它仅用来处理 DNS 查询,但由于其受到浏览器的广泛支持,且缩短了 DNS 的查询时间的效果显著,所以使用场景十分普遍。详细参考:预解析 dns-prefetch、
预提取
前面介绍的预加载和预连接,都是试图使所需的关键资源或关键操作更快地获取或发生,这里介绍的预提取,则是利用机会让某些非关键操作能够更早发生。
这个过程的实现方式是根据用户已发生的行为来判断其接下来的预期行为,告知浏览器稍后可能需要的某些资源。也就是在当前页面加载完成后,且在带宽可用的情况下,这些资源将以 Lowest 的优先级进行提起。
显而易见,预提取最适合的场景是为用户下一步可能进行的操作做好必要的准备,如在电商平台的搜索框中查询某商品,可预提取查询结果列表中的首个商品详情页;或者使用搜索查询时,预提取查询结果的分页内容的下一页:
<link rel="prefetch" href="page-2.html">
需要注意的是,预提取不能递归使用,比如在搜索查询的首页 page-1.html
时,可以预提取当前页面的下一页 page-2.html
的 HTML 内容,但对其中所包含的任何额外资源不会提前下载,除非有额外明确指定的预提取。
另外,预提取不会降低现有资源的优先级,比如在如下 HTML 中:
<html>
<head>
<link rel="prefetch" href="style.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
Hello World!
</body>
</html>
可能你会觉得对 style.css
的预提取声明,会降低接下来 <link rel="stylesheet"href="style.css">
的优先级,但其真实的情况是,该文件会被提取两次,第二次可能会使用缓存,如下图所示:
显然两次提取对用户体验来说非常糟糕,因为这样不但需要等待阻塞渲染的 CSS,而且如果第二次提取没有命中缓存,必然会产生带宽的浪费,所以在使用时应充分考虑。
小结
对于加载方面的优化原则可以概括为两点:
- 尽快呈现给用户尽可能少的必备资源
- 充分利用系统或带宽的空闲时机,来提前完成用户稍后可能会进行的操作过程,或加载将要请求的资源文件