Web 前端性能优化之三:加载优化

2、加载优化

1、延迟加载

本着节约不浪费的原则,在首次打开网站时,应尽量只加载首屏内容所包含的资源,而首屏之外涉及的图片或视频,可以等到用户滚动视窗浏览时再去加载。以上就是延迟加载优化策略的产生逻辑,通过延迟加载"非关键"的图片及视频资源,使得页面内容更快地呈现在用户面前。这里的"非关键"资源指的就是首屏之外的图片或视频资源,相较于文本、脚本等其他资源来说,图片的资源大小不容小觑。

〈img〉标签的src属性可以先使用同一个 Base64。该Base64图片仅仅是在真实图片显示出来前用以占位的,当页面发生滚动时,之前未出现在视窗中的商品出现在视窗中后,其商品图片的真实URL会被替换到〈img〉标签的src属性上,进而发起资源请求。

1、实现图片的延迟加载:Intersection Observer 方式

现代浏览器已大多支持了Intersection Observer API,可以通过它来检查目标元素的可见性,这种方式的性能和效率都比较好。

每当因页面滚动或窗口尺寸发生变化,使得目标元素(target)与设备视窗或其他指定元素产生交集时,便会触发通过Intersection Observer API配置的回调函数,在该回调函数中进行延迟加载的逻辑处理,会比传统方式(滚动监听等)显得更加简洁而高效。

以下便是Intersection Observer方式的具体实现,此方式仅需创建一个新的Observer,并在类名为lazy的〈img〉标签进入视窗后触发回调:

这种方式判断元素是否出现在视窗中更为简单直观,应在实际开发中尽量使用,但其问题是并非所有浏览器都能兼容

在将这种方式引入项目之前,应当确保已做到以下两点:

(1)做好尽量完备浏览器兼容性检查,对于兼容Intersection Observer API的浏览器,采用这种方式进行处理,而对于不兼容的浏览器,则切换回传统的实现方式进行处理。

(2)使用相应兼容的polyfill插件

2、实现图片的延迟加载:CSS 类名方式

这种实现方式通过CSS的background-image属性来加载图片,与判断〈img〉标签src属性是否有要请求图片的URL不同,CSS中图片加载的行为建立在浏览器对文档分析基础之上。

具体来说,当DOM树、CSSOM树及渲染树生成后,浏览器会去检查CSS以何种方式应用于文档,再决定是否请求外部资源。如果浏览器确定涉及外部资源请求的CSS规则在当前文档中不存在时,便不会去请求该资源。

具体的实现方式是通过JavaScript来判断元素是否出现在视窗中的,当在视窗中时,为其元素的class属性添加visible类名。而在CSS文件中,为同一类名元素定义出带.visible和不带.visible的两种包含background-image规则。

不带.visible的图片规则中的background-image属性可以是低分辨率的图片或Base64图片,而带.visible的图片规则中的background-image属性为希望展示的真实图片URL。

具体JavaScript的实现过程,即判断图片元素是否出现在视窗内的逻辑,与 Intersection Observer方式相同。同样为了确保浏览器的兼容性,在实际应用中应确保提供回退方案或polyfill。

3、原生的延迟加载支持

从Chrome 75版本开始,已经可以通过〈img〉和〈iframe〉标签的loading属性原生支持延迟加载了,loading属性包含以下三种取值。

●lazy:进行延迟加载。

●eager:立即加载。

●auto:浏览器自行决定是否进行延迟加载。

若不指定任何属性值,loading默认取值auto。下面是具体的代码使用场景:

此方式需要考虑兼容性:

2、视频加载
1、不需要自动播放的情况

由于Chrome等一些浏览器会对视频资源进行预加载,即在HTML完成加载和解析时触发DOMContentLoaded事件开始请求视频资源,当请求完成后触发window.onload事件开始页面渲染,过程如图:


视频资源的加载

为了使页面更快地加载并渲染出来,可以阻止不需要自动播放的视频的预加载,其方法是通过视频标签的preload进行控制:

〈video〉标签的preload属性通常的默认值为auto,表示无论用户是否希望,所有视频文件都会被自动下载,这里将其设置为none,来阻止视频的自动预加载。同时这里还通过poster属性为视频提供占位符图片,它的作用是当视频未加载出来时,不至于在页面中呈现一块让用户未知的空白。考虑类似边缘异常场景是必要的,因为浏览器对视频的加载行为可能存在较大差别。

2、视频替代 GIF 动画(自动播放的视频)

应当尽量用视频代替尺寸过大的GIF动画。

这里进行了两处修改:首先是为〈video〉标签添加了poster属性,意为使用poster中指定的图片作为视频延迟加载出现前的占位;其次是使用了类似应对图像延迟加载的方式,将真实视频资源的URL放在data-src属性中,然后基于Intersection Observer用JavaScript实现对延迟加载的控制:

3、首屏加载

对性能优化工作来说,不存在一蹴而就的解决方案,而是需要根据具体场景采用恰当的方式。

比如对于首屏上的内容就不应当进行延迟加载,而应使用正常加载的方式。

若将首屏视窗边界线作为延迟加载触发的阈值,其实并非最佳的性能考虑。更理想的做法是,在延迟加载的媒体资源到达首屏边界之前设置一个缓冲区,以便媒体资源在进入视窗之前就开始进行加载。

例如在使用Intersection Observer方式实现延迟加载判断时,可以通过配置options对象中的rootMargin属性来建立缓冲区:

观察可知rootMargin的值与CSS中margin属性值类似,上述代码中在屏幕视窗下设置了一个宽度为256px的缓冲区,这意味着当媒体元素距离视窗下边界小于256px时,回调函数就会执行开始资源的请求加载。而对于使用滚动事件处理来实现延迟加载的传统实现方式,也只需要更改getBoundingClientRect的设置,包括进入一个缓冲区即可实现类似的效果。

4、资源占位

当延迟加载的媒体资源未渲染出来之前,应当在页面中使用相同尺寸的占位图像。如果不使用占位符,图像延迟显示出来后,尺寸更改可能会使页面布局出现移位。

用来占位的图像解决方案也有多种,十分简单的方式是使用一个与目标媒体资源长宽相同的纯色占位符,或者像之前使用的Base64图片,当然也可以采用LQIP或SQIP等方法。

其中LQIP的全称是低质量图片占位符,即使用原图的较低分辨率版本来占位,SQIP则是一种基于SVG的LIQP技术。

5、内容加载失败

图像资源可以采取如下方案进行规避:

6、图像解码延迟

渐进式的JPEG会先呈现出一个低像素的图像版本,随后会慢慢呈现出原图的样貌。这是因为图像从被浏览器请求获取,再到最终完整呈现在屏幕上,需要经历一个解码的过程,图像的尺寸越大,所需要的解码时间就越长。

为减少此类卡顿现象,可以采用decode方法进行异步图像解码后,再将其插入DOM结构中。但目前这种方式在跨浏览器场景下并不通用,同时也会复杂化原本对于媒体资源延迟加载的处理逻辑,所以在使用中应进行必要的可用性检查。下面是一个使用Image.decode()函数来实现异步解码的示例:

7、JavaScript 是否可用

在通常情况下,我们都会假定JavaScript始终可用,但在一些异常不可用的情况下,开发者应当做好适配,不能始终在延迟加载的图像位置上展示占位符。可以考虑使用〈noscript〉标记,在JavaScript不可用时提供图像的真实展示:

如果上述代码同时存在,当JavaScript不可用时,页面中会同时展示图像占位符和〈noscript〉中包含的图像,为此我们可以给〈html〉标签添加一个no-js类:

在由〈link〉标签请求CSS文件之前,在〈head〉标签结构中放置一段内联脚本,当JavaScript可用时,用于移除no-js类:

以及添加必要的CSS样式,使得在JavaScript不可用时屏蔽包含.lazy类元素的显示:

当然这样并不会阻止占位符图像的加载,只是让占位符图像在JavaScript不可用时不可见,但其体验效果会比让用户只看到占位符图像和没有意义的图像内容要好许多。

8、资源优先级

浏览器向网络请求到的所有数据,并非每个字节都具有相同的优先级或重要性。所以浏览器通常都会采取启发式算法,对所要加载的内容先进行推测,将相对重要的信息优先呈现给用户,比如浏览器一般会先加载CSS文件,然后再去加载JavaScript脚本和图像文件。但即便如此,也无法保证启发式算法在任何情况下都是准确有效的,可能会因为获取的信息不完备,而做出错误的判断。

1、优先级

浏览器基于自身的启发式算法,会对资源的重要性进行判断来划分优先级,通常从低到高分为:Lowest、Low、High、Highest等。

比如,在〈head〉标签中,CSS文件通常具有最高的优先级Highest,其次是〈script〉标签所请求的脚本文件,但当〈script〉标签带有defer或async的异步属性时,其优先级又会降为Low。我们可以通过Chrome的开发者工具,在network页签下找到浏览器对资源进行的优先级划分:


浏览器的资源优先级

1、预加载

使用〈link rel="preload"〉标签告诉浏览器当前所指定的资源,应该拥有更高的优先级,例如:

这里通过as属性告知浏览器所要加载的资源类型,该属性值所指定的资源类型应当与要加载的资源相匹配,否则浏览器是不会预加载该资源的。在这里需要注意的是,〈link rel="preload"〉会强制浏览器进行预加载,它与其他对资源的提示不同,浏览器对此是必须执行而非可选的。因此,在使用时应尽量仔细测试,以确保使用该指令时不会提取不需要的内容或重复提取内容。

如果预加载指定的资源在3s内未被当前页面使用,则浏览器会在开发者工具的控制台中进行警告提示,该警告务必要处理:


预加载警告

可以使用〈link rel="preload"〉来让浏览器立即获取所需的字体文件:

这里的crossorigin属性非常重要,如果缺失该属性,浏览器将不会对指定的字体进行预加载。

2、预连接

通过〈link rel="preconnect"〉标签指令,告知浏览器当前页面将与站点建立连接,希望尽快启动该过程。虽然这么做的成本较低,但会消耗宝贵的CPU时间,特别是在建立HTTPS安全连接时。如果建立好连接后的10s内,未能及时使用连接,那么浏览器关闭该连接后,之前为建立连接所消耗的资源就相当于完全被浪费掉了。

另外,还有一种与预连接相关的类型〈link rel="dns-prefetch"〉。

3、预提取

前面介绍的预加载和预连接,都是试图使所需的关键资源或关键操作更快地获取或发生,这里介绍的预提取,则是利用机会让某些非关键操作能够更早发生。

显而易见,预提取最适合的场景是为用户下一步可能进行的操作做好必要的准备,如在电商平台的搜索框中查询某商品,可预提取查询结果列表中的首个商品详情页;或者使用搜索查询时,预提取查询结果的分页内容的下一页:

相关推荐
腾讯TNTWeb前端团队3 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰7 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪7 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪7 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy7 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom8 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom8 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom8 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom8 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom8 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试