懒加载在前端性能优化的应用及原理

背景是在某项目中的其中一个页面,接入现场真实数据后,加载时间很长,长达10几秒,严重影响用户体验。

排查缘由

chromeDevTools中,清缓存硬加载,可模拟用户第一次访问页面的场景。根据Network中的若干指标,对性能瓶颈初步判断:前端

  • requests:在HTTP / 1.0HTTP / 1.1链接上,Chrome每一个主机最多容许六个同时TCP链接,因此请求数过多会致使TTFB等待时间过长。
  • xhrajax接口时间过长,瓶颈在于后端接口。
  • FinishFinish时间远远大于DOMContentLoadedLoad时间,说明页面中的请求资源很大

优化方向

  1. 对于请求数过多的瓶颈,简单来讲就是要减小请求数量。自上而下讨论,客户端(浏览器)页面要减小请求数量,将首屏不可见的资源放在首屏以后请求,将一些阻塞页面渲染的请求预加载或者懒加载;利用HTTP 2的多路复用特性,合并请求;服务端渲染。
  2. ajax接口时间过长,主要在后端接口的优化,Nodejs(BFF层)基于微服务的能力,因此要加快服务调用速度,减小服务调用数量,在业务上进行优化(缓存、批量接口、非必要数据异步请求...),固然自身代码层的运行效率也要考虑。
  3. 请求资源过大过多的问题,可优化img这类图片为懒加载(下文会提到),当大数据塞到数组对象里遍历渲染的时候,对不可见的组件进行懒加载,节省性能及页面渲染时间的开支;压缩图片格式,例如WebP格式

对比 PNG 原图、PNG 无损压缩、PNG 转 WebP(无损)、PNG 转 WebP(有损)的压缩效果图
  1. 从用户体验角度出发,loading动画、图片的渐进式渲染(浏览器对一张图片的加载顺序基本上是下载了多少展现多少,让用户感受很刻板生硬,渐进式渲染就是图片的内容从模糊到清晰的过程)、预加载(预见性的加载一些不可见区域的资源,提升用户在快速滚动浏览器时候的体验)。

懒加载的实践应用

上述优化方向中,做者优化了 Nodejs层的 api接口时间,缓存了部分业务逻辑,剥离了部分底层接口使其异步获取;同时选择懒加载优化方向,对前端组件及图片资源的加载进行优化。优化结果,肉眼可见。

懒加载并非一个新鲜的名词,顾名思义,就是懒,如今不加载,稍后再加载,换个词说就是,按需加载。由于不少场景下,暂时看不见用不到的资源是不须要同时加载的,浪费时间开支同时,也消耗了没必要要的CPUIO等资源。例如图片懒加载在jQuery时代就已经十分普及,在reactvue等前端MV*框架的出现后,组件懒加载,SPA单页应用中的路由懒加载,webpack中对初始化不须要加载的代码块进行懒加载,从而优化性能...如下做者重点介绍下图片懒加载及vue组件懒加载。vue

图片懒加载

对于一些视频图片web应用,图片懒加载几乎是必需要作的,能够大大提高用户体验。

原理解析

  1. 将须要懒加载的img标签的src设置缩略图或者不设置src,这里的占位图能够是缺省图,loading图;
  2. 判断该img标签是否在浏览器可视区域,若是在可视区域,则将真实的图片url设置到img标签的src属性;
  3. 用户滚动浏览器,遍历须要懒加载的标签,根据步骤2判断并执行;

判断元素是否在浏览器可视区域

做者认为这是懒加载最重要的环节
getBoundingClientRect
MDN中的定义: Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。

// 获取元素的getBoundingClientRect属性
const rect = Element.getBoundingClientRect();

if(rect.top < document.documentElement.clientHeight) {
    // 将top值与页面的clientHeight进行对比,若小于则为可视区域
    ...
}

PS:该方案须要监听scroll事件,注意节流处理。react

Intersection Observer
MDN中的定义: IntersectionObserver接口 (从属于 Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗( viewport)交叉状态的方法。Intersection Observer API 容许你配置一个回调函数,每当目标(target)元素与设备视窗或者其余指定元素发生交集的时候执行。设备视窗或者其余元素咱们称它为根元素或根(root)。
var options = {
    root: document.querySelector('#scrollArea'), 
    rootMargin: '0px', 
    threshold: 1.0 // 目标(target)元素与根(root)元素之间的交叉度是交叉比(intersection ratio), 取值在0.0和1.0之间
}

var observer = new IntersectionObserver(() => {
    // 回调函数,当目标元素和根元素交叉时触发
    ...
}, options);

var target = document.querySelector('#listItem');
// 添加目标元素,与根元素进行交叉状态比对
observer.observe(target);

PS:该方案较前者的优势就是不须要监听,其实兼容性在chrome中还不错。webpack

vue 组件懒加载

这里的懒加载判断依据和图片相似,一样是要判断可视或者即将可视的时机,来控制组件的加载与否。当加载条件为false时,不作渲染,为true时则渲染,这里用v-if指令就能够实现。git

在条件切换的同时,最好加入相似骨架屏的页面,来过渡用户体验。github

项目实践

社区里这样的方案有不少,评估后决定采用 vue-lazyload,star 5.7k,recent updates is 2 months ago,很稳。

引入

npm i vue-lazyload -S

// 在入口js中引入依赖,注册在vue实例上
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  lazyComponent: true
});

这里简单提一下该方案的组件懒加载方案,在源码L11-L16,利用render来生成组件的内容this.$slots.default,十分巧妙。web

render (h) {
   if (this.show === false) {
        return h(this.tag)
   }
   return h(this.tag, null, this.$slots.default)
}

组件应用

// 原代码
<div class="camera-card-img" :style="{'backgroundImage': 'url(' + data._thumbnails + ')'}">

// 加入图片懒加载逻辑
<div class="camera-card-img" v-lazy:background-image="data._thumbnails">

<lazy-component>
    // 须要懒加载的组件
    ...
</lazy-component>

优化结果

调用真实数据,控制变量,优化前:

52 requests, 19 imgajax

加入图片懒加载:

38 requests, 12 imgchrome

当浏览器继续滚动的时候,图片依次加载,能够看到network中的request逐步增长至52,说明加载了剩余图片。npm

组件懒加载也是一样的效果,数据量小可能页面finish时间差感知不明显,能够加大模拟量至上千:

Finish时间10s,

Finish时间4s,速度提高显著。

PS:这里先后请求数不变的缘由,是做者在模拟数据的时候重复了若干次真实数据,致使资源地址都是重复的,浏览器会缓存请求,因此致使请求数不变。

后续计划

其实能够看到,数据量大的时候,加载速度依旧很慢,还须要继续优化,能够从如下几个方向:

  • 缩略图格式(压缩资源大小)
  • 优化webpack打包,从代码块层面按需加载组件
  • 懒加载依然"不够懒"
  • 解决火焰图中看到的占据很长时间,阻塞页面渲染的请求

总结

优化无止境,经常是花了大力气,收效甚微。须要考虑时间和资源成本,优先解决投入产出比高的优化方向。以上是做者在实际项目中遇到的优化问题,仅供你们参考。

相关文章
相关标签/搜索