背景是在某项目中的其中一个页面,接入现场真实数据后,加载时间很长,长达10几秒,严重影响用户体验。
排查原因
在chrome
的DevTools
中,清缓存硬加载,可模拟用户第一次访问页面的场景。根据Network
中的若干指标,对性能瓶颈初步判断:
requests
:在HTTP / 1.0
或HTTP / 1.1
连接上,Chrome
每个主机最多允许六个同时TCP
连接,所以请求数过多会导致TTFB
等待时间过长。xhr
:ajax
接口时间过长,瓶颈在于后端接口。Finish
:Finish
时间远远大于DOMContentLoaded
和Load
时间,说明页面中的请求资源很大优化方向
- 对于请求数过多的瓶颈,简单来说就是要减少请求数量。自上而下讨论,客户端(浏览器)页面要减少请求数量,将首屏不可见的资源放在首屏之后请求,将一些阻塞页面渲染的请求预加载或者懒加载;利用
HTTP 2
的多路复用特性,合并请求;服务端渲染。 ajax
接口时间过长,主要在后端接口的优化,Nodejs(BFF层)基于微服务的能力,所以要加快服务调用速度,减少服务调用数量,在业务上进行优化(缓存、批量接口、非必要数据异步请求…),当然自身代码层的运行效率也要考虑。- 请求资源过大过多的问题,可优化img这类图片为懒加载(下文会提到),当大数据塞到数组对象里遍历渲染的时候,对不可见的组件进行懒加载,节省性能及页面渲染时间的开支;压缩图片格式,例如WebP格式;
对比 PNG 原图、PNG 无损压缩、PNG 转 WebP(无损)、PNG 转 WebP(有损)的压缩效果图
- 从用户体验角度出发,
loading
动画、图片的渐进式渲染(浏览器对一张图片的加载顺序基本上是下载了多少展示多少,让用户感觉很刻板生硬,渐进式渲染就是图片的内容从模糊到清晰的过程)、预加载(预见性的加载一些不可见区域的资源,提高用户在快速滚动浏览器时候的体验)。
懒加载的实践应用
上述优化方向中,作者优化了
Nodejs
层的api
接口时间,缓存了部分业务逻辑,剥离了部分底层接口使其异步获取;同时选择懒加载优化方向,对前端组件及图片资源的加载进行优化。优化结果,肉眼可见。
懒加载并不是一个新鲜的名词,顾名思义,就是懒,现在不加载,稍后再加载,换个词说就是,按需加载。因为很多场景下,暂时看不见用不到的资源是不需要同时加载的,浪费时间开支同时,也消耗了不必要的CPU
、IO
等资源。例如图片懒加载在jQuery
时代就已经十分普及,在react
、vue
等前端MV*
框架的出现后,组件懒加载,SPA
单页应用中的路由懒加载,webpack
中对初始化不需要加载的代码块进行懒加载,从而优化性能…以下作者重点介绍下图片懒加载及vue
组件懒加载。
图片懒加载
对于一些视频图片web应用,图片懒加载几乎是必须要做的,可以大大提升用户体验。
原理解析
- 将需要懒加载的
img
标签的src
设置缩略图或者不设置src
,这里的占位图可以是缺省图,loading图; - 判断该img标签是否在浏览器可视区域,如果在可视区域,则将真实的图片url设置到img标签的src属性;
- 用户滚动浏览器,遍历需要懒加载的标签,根据步骤2判断并执行;
判断元素是否在浏览器可视区域
作者认为这是懒加载最重要的环节
getBoundingClientRect
MDN中的定义:
Element.getBoundingClientRect()
方法返回元素的大小及其相对于视口的位置。
1 | // 获取元素的getBoundingClientRect属性 |
PS:该方案需要监听scroll
事件,注意节流处理。
Intersection Observer
MDN中的定义:
IntersectionObserver
接口 (从属于Intersection Observer API
) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport
)交叉状态的方法。Intersection Observer API 允许你配置一个回调函数,每当目标(target)元素与设备视窗或者其他指定元素发生交集的时候执行。设备视窗或者其他元素我们称它为根元素或根(root)。
1 |
|
PS:该方案较前者的优点就是不需要监听,其实兼容性在chrome中还不错。
vue 组件懒加载
这里的懒加载判断依据和图片类似,同样是要判断可视或者即将可视的时机,来控制组件的加载与否。当加载条件为false
时,不做渲染,为true
时则渲染,这里用v-if
指令就可以实现。
在条件切换的同时,最好加入类似骨架屏的页面,来过渡用户体验。
项目实践
社区里这样的方案有很多,评估后决定采用 vue-lazyload,star 5.7k,recent updates is 2 months ago,很稳。
引入
1 | npm i vue-lazyload -S |
这里简单提一下该方案的组件懒加载方案,在源码L11-L16,利用render
来生成组件的内容this.$slots.default
,十分巧妙。
1 | render (h) { |
组件应用
1 | // 原代码 |
优化结果
调用真实数据,控制变量,优化前:
52 requests, 19 img
加入图片懒加载:
38 requests, 12 img
当浏览器继续滚动的时候,图片依次加载,可以看到network中的request逐步增加至52,说明加载了剩余图片。
组件懒加载也是同样的效果,数据量小可能页面finish时间差感知不明显,可以加大模拟量至上千:
Finish
时间10s,
Finish
时间4s,速度提升显著。
PS:这里前后请求数不变的原因,是作者在模拟数据的时候重复了若干次真实数据,导致资源地址都是重复的,浏览器会缓存请求,所以导致请求数不变。
后续计划
其实可以看到,数据量大的时候,加载速度依旧很慢,还需要继续优化,可以从以下几个方向:
- 缩略图格式(压缩资源大小)
- 优化webpack打包,从代码块层面按需加载组件
- 懒加载依然”不够懒”
- 解决火焰图中看到的占据很长时间,阻塞页面渲染的请求
总结
优化无止境,常常是花了大力气,收效甚微。需要考虑时间和资源成本,优先解决投入产出比高的优化方向。以上是作者在实际项目中遇到的优化问题,仅供大家参考。