首屏、白屏与卡顿性能优化?你想要的都在这里!

980 阅读6分钟

您好, 如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想

首屏秒开

首屏秒开主要可以分为 4 个方法——懒加载,缓存,离线化,并行化。

懒加载

懒加载是指在长页面加载过程时,先加载关键内容,延迟加载非关键内容。比如当我们打开一个页面,它的内容超过了浏览器的可视窗口大小,我们可以先加载前端的可视区域内容,剩下的内容等它进入可视区域后再按需加载。

缓存

懒加载本质是提供首屏后请求非关键内容的能力,那么缓存则是赋予二次访问不需要重复请求的能力。在首屏优化方案中,接口缓存和静态资源缓存起到中流砥柱的作用。

接口缓存

对于不常变的数据可以做本地缓存,比如一些配置接口。同时还可以进行版本号管理,后端返回数据时同时返回版本号,在数据发生变更时(可以使用监听机制),后端将版本号更新。前端请求时携带之前拿到的版本号,如果版本号一致,后端直接返回无变化,不用再查数据库。

静态资源缓存

资源长期不变的话,比如 1 年都不怎么变化,我们可以使用强缓存,如 Cache-Control 来实现。具体来说可以通过设置 Cache-Control:max-age=31536000,来让浏览器在一年内直接使用本地缓存文件,而不是向服务端发出请求。

至于第二种,如果资源本身随时会发生改动的,可以通过设置 Etag 实现协商缓存。具体来说,在初次请求资源时,设置 Etag(比如使用资源的 md5 作为 Etag),并且返回 200 的状态码,之后请求时带上 If-none-match 字段,来询问服务器当前版本是否可用。如果服务端数据没有变化,会返回一个 304 的状态码给客户端,告诉客户端不需要请求数据,直接使用之前缓存的数据即可。

离线化

离线化是指线上实时变动的资源数据静态化到本地。打包构建时预渲染页面,前端请求落到 index.html 上时,已经是渲染过的内容。此时,可以通过 Webpack 的 prerender-spa-plugin 来实现预渲染,进而实现离线化。Webpack 实现预渲染的代码示例如下:

// webpack.conf.js
var path = require('path')
var PrerenderSpaPlugin = require('prerender-spa-plugin')
module.exports = {
  // ...
  plugins: [
    new PrerenderSpaPlugin(
      // 编译后的html需要存放的路径
      path.join(__dirname, '../dist'),
      // 列出哪些路由需要预渲染
      [ '/', '/about', '/contact' ]
    )
  ]
}

并行化

懒加载、缓存和离线化都是在请求本身上下功夫,想尽办法减少请求或者推迟请求,并行化则是在请求通道上功夫,解决请求阻塞问题,进而减少首屏时间。

代码逻辑上,能并行的逻辑尽量并行处理,如使用Promise.all()。根本上可以使用HTTP 2.0 的多路复用方案来解决。突破同域名的连接数限制(6个),解决HTTP阻塞问题。

白屏优化

所谓白屏时间,一般是当用户打开一个页面,从开始等待到页面第一个字符出现的时间。我们可以基于影响白屏时间长短的两个主要因素来解决——DNS 查询和首字符展示

DNS 查询优化

前端侧,可以通过在页面中加入 dns-prefetch,在静态资源请求之前对域名进行解析,从而减少用户进入页面的等待时间。如下所示:

<meta http-equiv="x-dns-prefetch-control" content="on" /><link rel="dns-prefetch" href="https://s.google.com/" >

其中第一行中的 x-dns-prefetch-control 表示开启 DNS 预解析功能,第二行 dns-prefetch 表示强制对 s.google.com 的域名做预解析。这样在 s.google.com 的资源请求开始前,DNS 解析完成,后续请求就不需要重复做解析了。

首字符展示优化

方案一是使用loading图,但是体验稍差。

方案二可以使用骨架屏,可以使用切图,但是可能整页切图质量较大,也会占用网络资源。也可以参考社区自动化方案如page-skeleton-webpack-plugin

卡顿

如果页面出现连续 5 帧超过 50ms ,这就属于严重卡顿。如何处理呢?

首先也还是问题的定位,先通过 charles 等工具抓包看一下数据接口,如果是和数据相关的问题,找后端同事,或者用数据缓存的方式解决。如果问题出在前端,一般和以下两种情形有关:浏览器的主线程与合成线程调度不合理,以及计算耗时操作

浏览器的主线程与合成线程调度不合理

一般来说,主线程主要负责运行 JavaScript,计算 CSS 样式,元素布局,然后交给合成线程,合成线程主要负责绘制。当使用 height、width、margin、padding 等作为 transition 值时,会让主线程压力很大。此时我们可以使用 transform 来代替直接设置 margin 等操作。原理相关可以参考简析浏览器原理

计算耗时操作

除了主线程和合成线程调度不合理导致的卡顿,还有因为计算耗时过大导致的卡顿。遇到这类问题,一般有两种解法:空间换时间和时间换空间。

空间换时间方面,比如你需要频繁增加删除很多 DOM 元素,这时候一定会很卡,在对 DOM 元素增删的过程中最好先在 DocumentFragment (DOM文档碎片)上操作,而不是直接在 DOM上操作。只在最后一步操作完成后,将所有 DocumentFragment 的变动更新到 DOM上,从而解决频繁更新 DOM 带来的卡顿问题。

至于时间换空间,一般是通过将一个复杂的操作细分成一个队列,然后通过多次操作解决复杂操作的问题,如使用分割队列(类似于Vue批量更新):

window.requestAnimationFrame = (function(){
  return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function(callback){
      window.setTimeout(callback,1000/60)
    }
}())

image.png

如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想。