乐趣区

关于前端:页面白屏时间和首屏时间优化

前言

人不知; 鬼不觉又马上过年了,忽然意识到往年居然没写一篇技术文章,整个上半年工作比拟忙碌,下半年节奏缓下来之后也因为太久没动笔了就始终没动笔,可见懈怠是会习惯的。于是趁着年前,把往年工作中做的一些比拟有意思的货色梳理一下做个总结吧。这篇文章就先讲讲往年着重做的页面性能优化上的一些教训。

背景

现有业务中有个的 3D 场景的预览页始终在进行性能迭代,然而始终没有关注页面的白屏工夫和首屏工夫,平时本人用手机关上也挺快的,偶然接到反馈说页面加载工夫太久的也没怎么器重。直到某一天有同一批客户反馈页面关上工夫十分久,通过和客户的沟通后发现问题呈现的起因是因为他们所在区域的网络环境不是很好。正是因为这个契机,让咱们意识到不能以平时本人所处的失常网络作为衡量标准,同时页面关上速度还会受到手机自身性能的影响。而应该满足大多数状况下页面关上的工夫是能够被承受的,因而咱们决定对该预览页面的白屏工夫和首屏工夫进行一次彻底的优化。为什么是白屏工夫和首屏工夫呢,因为这是最能反馈出页面的用户体验的两个指标,用户对页面好坏的第一感触就在于这个关上工夫。

指标设立

咱们找到了行业内的标杆产品的页面和咱们本人产品的页面进行比照。在公司内的页面性能测试平台上,选取了中端机型,中等网络等雷同的测试条件下进行了多轮比照测试。后果让咱们难堪的是,在这一雷同条件下,友商页面的均匀数据是:白屏工夫 500ms 左右,首屏工夫:3.5s 左右。而咱们的页面数据是:白屏工夫 2s 左右,首屏 12s 左右。面对较大的差距,在羞愧没有关注页面性能的同时,也为咱们找到了优化的指标:在优化后,还是在这一雷同测试条件下,咱们页面的白屏工夫和首屏工夫不慢于业内的标杆竞品。

优化白屏工夫

对于白屏工夫,咱们页面原先的逻辑是这样的:当页面中的获取配置的接口(暂且称为 getBasicConfig 接口,获取页面的题目、logo 等根底配置)返回后,拿到 logo 图片的配置 url 后加载 logo 图片。看上去没什么大问题,白屏工夫的耗费次要在两个中央:

  1. 页面 index.html 加载后,index.js 和 index.css 加载消耗的工夫(单页面利用,产物为 index.html,index.js,index.css)
  2. getBasicConfig 接口消耗的工夫

因而基于这两点进行针对性的优化,首先看第一点,为了节俭文件资源加载消耗的工夫,咱们的做法是把加载 logo 的逻辑放到 index.html 中,这样就不必等到其余两个文件加载实现就能够进行 logo 的加载了。具体的做法也很简略,在 html 中间接写 logo 的 css 布局和压缩后的 js 加载逻辑,大抵的成果如下:

再看第二点接口耗时的问题,其实咱们这里的业务是因为须要反对用户自定义 logo 的设置,所以须要从接口中拿到 logo 配置,如果是固定的 logo 基本就没有这步的优化了。为了解决这个问题,同时思考到前面的首屏工夫的优化,去接口化是一个很好的形式。鉴于咱们的页面渲染走的是服务端渲染,且这个页面配置的接口没有敏感信息,因而最终咱们去掉了 getBasicConfig 接口,而是将数据通过服务端渲染时塞一个全局配置 window.config 进来,节俭了 getBasicConfig 接口响应的工夫消耗。这样在 html 中就能取到以后页面的 logo 配置 url 了。

通过这两步的优化,页面的白屏工夫等于齐全取决于 index.html 的加载工夫了,而 index.html 的加载是最快的,优化实现后咱们的白屏工夫在等同测试条件下胜利放大到 500ms 以内。总结一下白屏工夫的优化次要是两点:

  1. 将 logo 的加载提前到 html 中进行,而不是传统的 js 文件逻辑中(如果非要在 js 文件逻辑中,能够思考如何缩小 js 的体积,也能起到优化成果)
  2. 如果有接口的依赖,尽量去掉接口的依赖(通过服务端数据注入,或者在业务容许的状况下写死页面 logo 地址)

优化首屏工夫

首屏工夫的优化是个非常复杂的过程,这里补充一点就是如何定于首屏工夫其实也是一个要害的点,是采纳 fcp 或者 lcp,还是本人依据业务自定义一个埋点机会作为首屏工夫,这外面也有一些考究。这里就不具体开展了,咱们页面的首屏工夫选取的是 3D 页面渲染实现的工夫点,通过自定义埋点拿到,具体的体现大抵就是 logo 加载实现的工夫(logo 隐没的工夫就是监听到 3D 页面渲染实现的事件触发),这就很容易从比照 h5 性能测试平台上,每一帧页面的截图序列中分明获取到,logo 完结的下一张截图就是咱们比照的首屏工夫。

通过一系列的优化伎俩,最终咱们页面的首屏工夫也胜利达到了预期指标。上面就基于页面的整个加载过程,具体聊一下首屏工夫的优化点:

  1. 压缩 index.js 的体积,页面一开始就会加载 index.js,其加载工夫间接算到首屏工夫中,因而首当其冲须要做的是优化 index.js 的体积(index.css 个别都比拟小,优化意义不大)。咱们页面的 index.js 的原来大小在 1.2mb,这显著是有很大优化空间的,通过优化后,页面的包体积胜利从 1.2mb 升高到 750kb。包体积的优化的一些关键点:

    第一步:通过可视化包体积剖析工具,从体积占比从大到小找到须要优化的指标
    第二步:进行针对性的优化,从以下一些角度寻找优化点:

    • 大文件模块的同类代替

      • case1:找能能满足需要,然而体积小很多的同类模块包替换
      • case2:如果引入某个包只用到了其余的某个办法,能够思考去掉该包,本人手写这个性能
    • 大文件的 cdn 引入代替 npm 包(cdn 引入会带来 http 加载的耗时,因而成果受到文件大小的影响,原则上越大的文件成果会越好,实在成果须要理论测试)
    • 工具包的按需引入(如 antd、lodash)
    • 动态资源图片的解决

      • case1:大图片 cdn 化(同样面临 http 加载的耗时,实在成果须要理论测试)
      • case2:小图片的 base64 化解决(大图片的 base64 化后在页面中的解析耗时会较长,因而实在成果须要理论测试,小图片必定有成果)
    • 无用代码的去除,查看代码中是否有冗余或者无用代码
    • 无用 npm 包的去除,同样须要仔细查看
    • 打包配置中生产环境下开启 console.log 和正文代码的主动删除,去掉 sourceMap 配置
  2. 大文件的压缩,因为页面中有一些须要从 oss 上读取的 3D 模型相干的 obj、gltf 文件,以及贴图 jpg 文件,所以这一块的逻辑其实跟 index.js 一样,都是通过缩小资源体积的大小从而缩小 http 响应的工夫,来缩短首屏工夫
  3. 网络申请优化,网络申请间接与页面资源的响应工夫挂钩,因而这一块的优化是一个重点,能够做的优化项比拟多。这一块的准则也能够概括成 2 点:

    • 尽量减少不必要的 http 申请
    • 必要的 http 申请下,放慢申请的响应

    具体来说,咱们页面中做的工作如下:

    • 接口申请优化。

      • 尽量减少页面 loading 完结之前的接口申请数量,后面白屏工夫优化提到的去掉页面根本配置信息的接口 getBasicConfig,将数据通过服务端渲染注入到页面 window 下就是一个例子
      • 接口的异步加载优化,业务迭代的时候大概率接口是一批一批减少的,因而不同批次接口之间可能会呈现接口是同步 await 加载的,能够梳理下这一块逻辑,把没有依赖关系的接口进行并发地异步加载,即用 promise.all 同时解决多个无前后依赖的接口申请,节俭所有申请实现的总工夫
      • 接口的合并,通过梳理所有相干接口,把业务逻辑相干的接口进行合并申请
      • 无用或反复接口的查看,通过梳理所有相干接口,将一些无用或反复的接口废除
    • js、css 申请优化,一方面进行查看是否有无用或反复的资源申请,另一方面默认状况下这两种资源的下载和解析是会造成页面阻塞的,然而大多数状况下这些资源的异步加载是不会影响页面的失常解析的。所以针对这类资源能够进行如下操作:

      • 无用或反复资源申请的查看,删除这些冗余申请。或者将小资源内置到代码中(如一些第三方的 css 款式文件,如果文件内容不多,能够复制进去放在页面组件的 css 款式中,缩小一次网络申请)
      • script 里的 index.js 加 async
      • script 里第三方 js 资源 加 defer
      • link 里的 index.css 加 preload
    • 在申请链路上预加载资源,正当利用缓存。如果能预判到资源在将来的某个工夫点会被拜访,那么如果提前利用闲暇工夫线拜访一次,则会因为缓存的成果大大放慢后续真正拜访时的加载速度。而这就是 prefetch 和 dns-prefetch 的效用(具体的阐明自行查阅相干材料),留神这跟上一点提到的 preload 有肯定区别,前者针对的是以后页面的资源,后者针对的是将来可能拜访的页面资源。举咱们页面中的理论例子来阐明一下:咱们的预览页关上的门路其中有一种会通过一个列表页中查看详情进行关上,在这个列表页中蕴含了查问进去的所有预览页,且这些预览页的域名是雷同的。所以在列表页中,咱们就能够 dns-prefetch 预览页的域名地址,prefetch 预览页中的重要资源。这样在用户通过列表页关上某一个预览页中,预览页的加载速度会大大晋升。
  4. cdn 优化,这一块的优化针对的是有大量动态资源须要从 cdn 上获取的场景,比方咱们预览页中就有大量的图片资源须要从 cdn 读取,因而有着十分显著的成果,具体来说优化伎俩有上面一些:

    • 如果没接入 cdn 服务的,接入 cdn 服务 。很多状况下资源就间接存在 oss 公共桶上,通过 oss 的提供的域名地址就能间接拜访,如果是这种情景的,接入一下 cdn,通过 cdn 域名进行资源拜访,访问速度会有所晋升
    • 利用 cdn 缓存能力,开启 cdn 缓存和进行 cdn 预热,放慢页面二次拜访时关上的速度,开启缓存的形式具体依据接入的 cdn 供应商而定,个别默认都是开启缓存的。如果没关上的话,能够关上这一配置开关。cdn 预热相当于提前拜访一遍资源,这样哪怕资源第一次被用户拜访也曾经能命中 cdn 缓存了
    • 晋升 cdn 的配置,cdn 服务依据具体情况又有更粗疏的辨别,如果老本足够,能够思考以下措施,对资源访问速度有进一步晋升:

      • 晋升 cdn 的节点数量,特地是如果有国内外拜访需要,国内的也要思考东西南北的地区影响。多设立几个节点,且凑近访问量高的地区
      • cdn 减速,cdn 带宽扩容,放慢响应速度
  5. 业务逻辑优化,这一块的须要依据具体的业务场景针对性的采取优化措施,可能没什么优化空间,也可能会起到无足轻重的作用。优化的准则在于:尽可能减少 loading 完结之前的业务逻辑。举两个咱们页面中理论的例子来看一下:

    • 原来在页面 loading 完结之前,还有获取该场景下“流动轨迹”的逻辑,具体包含获取相干接口数据,解决数据,存在页面 store 中。而这个数据具体用到的工夫是在用户手动点击页面中的一个“开启轨迹”按钮后,所以齐全能够滞后这一块的业务逻辑。在页面首屏加载进去之后,再去做这一块逻辑的解决。通过这种逻辑时序的改变,能够放慢首屏的渲染速度
    • 原来页面 loading 完结的机会,是承受到 3D 场景触发的一个 sceneLoaded 事件。所以咱们钻研的重点转为是否进步这个 3D 场景 sdk 中事件的触发机会,通过对这个 sdk 的优化,最终咱们胜利把这个事件的触发机会提前了不少,从而达到缩短页面首屏工夫的目标

优化过程

整个优化过程中抛开具体的优化措施,正当制订优化打算,分阶段进行也是十分重要的一点。一开始肯定要找准主要矛盾,影响页面加载性能的因素会有很多,然而每一项因素影响的比重是齐全不一样的,这个须要根据测试的数据以及开发的教训去正当判断,并将影响因素依照从大到小的程序排序,先去解决影响大的因素,解决一个大的影响因素所起的成果很可能比解决 10 个小的影响因素来的更好。

整个优化过程不是一触而就的,能够划分为多个工夫节点进行成果的验证。一方面能及时看到成果,有个正向反馈。另一方面也能有个布局,有节奏的晋升优化的效率。

咱们页面的这次的整个优化过程大抵就分了三个阶段进行:

第一次优化

次要进行了页面 js 体积包压缩和大文件压缩,优化后,首屏工夫缩小到 9.5s 左右。

第二次优化

次要进行了网络申请优化,优化后,首屏工夫缩小到 7.5s 左右。

第三次优化

次要进行了 cdn 优化和业务逻辑的优化,优化后,首屏工夫缩小到 3.5s 左右。

感触

在业务疾速迭代的过程中,页面性能往往容易被忽视。然而像白屏工夫和首屏工夫是用户体检好坏的第一感觉,这也恰好是施展着前端人造亲热用户侧的劣势和职责所在。通过这次优化,咱们页面的关上速度显著比之前快了不少,看着前后成果的比照,满足感油然而生,也心愿这些优化教训可能帮忙到有须要的敌人~

退出移动版