首屏加载的意义不言而喻,毕竟第一印象最重要,直接影响用户体验和留存。当用户使用你的产品的时候,一上来半天刷不出首页,很多用户往往就直接给你Ctrl+F4了。
那么问题来了,怎么做首屏优化。在了解怎么优化之前,我们需要知道首屏加载的几个重要时刻。
图片
- 首次加载
- 什么时候加载出页面
- 什么时候用户可以交互
为此,我们可以从以下几个方面来进行相关的优化。
资源体积太大
资源压缩与合并/代码拆分
将小图片内联为Data URL,也可以额减小HTTP的请求数量,需要注意的是,浏览器缓存并不会存储Data URL格式的图片,放在css的background-image属性中即可。由于使用Data URL在渲染和CPU消耗上更大,所以使用时也需要谨慎而不应该一味的滥用。
通过以上几种方法,我们主要要解决的问题是以下几个
- 减少HTTP请求数量
- 减少请求资源的大小
- 减少不必要的代码
需要注意的是,在CSS和JS合并的时候我们需要谨慎,并非所有CSS和JS合并都是好的,不能一味的为了做首屏或者性能优化而引发了其他方面的问题。在有若干个小文件的时候,或者是没有冲突的同模块的文件的时候是可以考虑合并的。但是如果我们把其他不同模块的CSS和JS也合并到了一起,可能会给后续的解析处理和自己的代码维护带来问题,而且JS文件间还可能会出现命名空间的冲突。这些都是无脑的资源合并会带来的问题。
传输压缩
- Gzip 需要注意的是,太小的文件启用Gzip以后可能反而会增大,这是因为压缩前会写入一段映射字典,但是这个是Byte层面的可以忽略不计。
HTTP2.0和HTTP1.1
图片
3630394917-5b229aa26f852_fix732.png
图上简单概括就是:
- HTTP1.1的keep-alive默认开启,而且keep-alive是按顺序请求返回,等到上一个请求返回以后才会进行下一个请求,会有阻塞问题。keep-alive的请求顺序如下:
- 1.请求style.css
- 2.返回style.css
- 3.请求script.js
- 4.返回script.js 如何判断是否开启了keep-alive可以从Response Headers看到,切记是HTTP1.1中的。
图片
- 但是HTTP2.0就不一样了,HTTP2.0的多路复用可以一次性发送多个请求,不一定是按顺序也不需要等待上一个请求返回。这些请求都有唯一标识,所以可以无序。
比较详细的内容在面试点之《HTTP协议与TCP/IP协议》[1]中有。
HTTP缓存
在此之前,我们要熟悉两个概念,强缓存和协商缓存。
图片
强缓存:浏览器直接从本地缓存中取数据,而不用去请求服务器。Expires/Cache-Control(优先级更高,且为通用字段,请求和返回报文中都可以使用)
图片
- Cache-Control常见的属性有很多:
- max-age:单位是秒,意为缓存的持续时间(寿命)。比如Cache-Control:max-age=60;意为60秒内会直接使用该缓存,而max-age import('./Foo.vue')
const Bar = () => import('./Bar.vue')const router = new VueRouter({
routes: [{
path: '/foo',
component: Foo
},{
path: '/bar',
component: Bar
}]
})- 图片懒加载 在我的《前端性能优化(二):图片[4]》文中已经讲了比较详细的图片懒加载在此就不多解释了。
预渲染
我使用的是Vue.js官网介绍使用的prerender-spa-plugin。这个插件的配置很多,它的GitHub地址在这里[5]。
Vue.js的官网中给到该预渲染插件如下描述:
图片
预渲染也有一些需要注意的点:
- 如果路由过多,成百上千(当然这应该是极少数情况)的时候,使用预渲染会非常缓慢。虽然说每次更新只用执行一次,但是需要的时间会非常长。
- 接口请求过多的时候不建议使用预渲染,而是使用SSR;如果没有SEO的需求,骨架屏是个更简单方便的选择,不需过多的配置更不需要后台配合。
- 预渲染不执行JavaScript,只试用于纯静态页面,当然你也可以配置等接口请求返回拿到数据以后再预渲染,这种仅仅适用于少量接口请求。而SSR比预渲染的不同就是多了一步执行JavaScript。
安装
npm i prerender-spa-plugin -D
webpack.prod.conf.js
const PrerenderSPAPlugin = require('prerender-spa-plugin') const Renderer = PrerenderSPAPlugin.PuppeteerRenderer const webpackConfig = merge(baseWebpackConfig, { plugins:[ new PrerenderSPAPlugin({ // 必需 - 要预渲染的 webpack 输出应用程序的路径。 staticDir: path.join(__dirname, '../dist'), // 必需 - 要渲染的路由 routes: ['/', '/about'], renderer: new Renderer({ inject: { foo: 'bar' }, // 渲染时显示浏览器窗口。用于调试。false意为打开,true是不打开 headless: false, // 可选 - 等待渲染,直到在文档上调度指定的事件。 // 例如,使用 `document.dispatchEvent(new Event('event-home'))` renderAfterDocumentEvent: 'event-home' }) }), ] })
main.js
new Vue({ el: '#app', store, router, components: { App }, template: '', mounted() { document.dispatchEvent(new Event('event-home')) } })
npm run build以后我们会得到如下的文件,这个时候我们会发现,是多了一个about的文件夹。这是由于配置了new PrerenderSPAPlugin中的routes。
图片
图片
而且此时我们会发现,在打包好的index.html文件中,id为app的div标签中有其他标签内容。倘若我们不使用预渲染,打包出的文件里,这个标签里是空的不会有任何标签。
图片
图片
SSR(服务端渲染)
- 分页首屏加载
- 各好的SEO
- 搜索排名很重要的时候
- 大型架构,动态页面,且面对公众用户
具体使用可参考Vue的官方文档
Service workers
Service workers可以很好的解决离线时候的首页加载问题。但是鉴于文章长度限制,就在此处不细说了。
优化资源加载的顺序
某乎有位大佬说的很详细,链接在此[6]。
prefetch
主要加载较晚才会用到的资源,告知浏览器后,浏览器就会在闲时去加载对应的资源,很适合在懒加载时使用。
对于使用prefetch获取资源,其优先级默认为最低Lowest,可以认为当浏览器空闲的时候才会去获取的资源。
图片
preload
主要用来加载当前页面很重要的资源。
浏览器通过as值能得知资源类型,还能根据as的值发送适当的Accept头部信息。甚至可以通过as来标识他们请求资源的优先级(比如说使用as="style"属性将获得最高的优先级,即使资源不是样式文件)
图片
两者的异同
- 如果关闭了浏览器,倘若请求还没有结束,preload会立刻结束请求,但是prefetch会继续请求。
- 如果preload还未下载完就已经开始解析所需资源,此时并不会二次请求,而是等待此次请求完毕。
- 倘若对同一个资源同时使用preload和prefetch则会导致二次加载。
- preload是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源,而prefetch是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源。
补充
关于Vue中的一些优化(并非首屏优化),我推荐黄轶老师的这篇文章《揭秘 Vue.js 九个性能优化技巧》[7]。
在我个人理解看来,性能优化的最终目的并不是完全追求时间上的长短,核心目的是给用户更好的体验,在提升了帧数的情况舍弃一点点加载或者渲染时间在整体体验上要比完全追求数值上的长短有意义的多。也就是上面文章中这位朋友分享的观点:
图片