首屏加载的意义不言而喻,毕竟第一印象最重要,直接影响用户体验和留存。当用户使用你的产品的时候,一上来半天刷不出首页,很多用户往往就直接给你Ctrl+F4了。
那么问题来了,怎么做首屏优化。在了解怎么优化之前,我们需要知道首屏加载的几个重要时刻。
图片
为此,我们可以从以下几个方面来进行相关的优化。
将小图片内联为Data URL,也可以额减小HTTP的请求数量,需要注意的是,浏览器缓存并不会存储Data URL格式的图片,放在css的background-image属性中即可。由于使用Data URL在渲染和CPU消耗上更大,所以使用时也需要谨慎而不应该一味的滥用。
通过以上几种方法,我们主要要解决的问题是以下几个
需要注意的是,在CSS和JS合并的时候我们需要谨慎,并非所有CSS和JS合并都是好的,不能一味的为了做首屏或者性能优化而引发了其他方面的问题。在有若干个小文件的时候,或者是没有冲突的同模块的文件的时候是可以考虑合并的。但是如果我们把其他不同模块的CSS和JS也合并到了一起,可能会给后续的解析处理和自己的代码维护带来问题,而且JS文件间还可能会出现命名空间的冲突。这些都是无脑的资源合并会带来的问题。
图片
3630394917-5b229aa26f852_fix732.png
图上简单概括就是:
图片
比较详细的内容在面试点之《HTTP协议与TCP/IP协议》[1]中有。
在此之前,我们要熟悉两个概念,强缓存和协商缓存。
图片
强缓存:浏览器直接从本地缓存中取数据,而不用去请求服务器。Expires/Cache-Control(优先级更高,且为通用字段,请求和返回报文中都可以使用)
图片
协商缓存:浏览器发送请求到服务器,服务器判断是否使用本地缓存。
Etag(响应头中)和If-None-Match(请求头中)【优先级更高】
图片
两者的值都是该资源的唯一标识字符串。第一次请求时,响应头(Response Headers)中会添加一个Etag的字段,再次请求服务器时,会在请求头报文(Request Headers)中添加If-None-Match字段,它的值就是上次响应头(Response Headers)中的Etag值。服务器会比较两个值是否相同。
需要特别注意的是,Etag变化并不代表文件内容一定变化,Etag的值取决于服务端的算法。比如Etag的值由最后一次修改时间+内容经过哈希算法而得,但是此时我编辑了内容并没有修改内容,最后一次的修改时间会发现变化,此时的Etag值肯定也会变化,但是内容并没有发生改变
图片
如图所示,两者的值都是GMT格式的时间字符串,Last-Modified意为最后一次修改文件的时间,其实这个方法和上一种原理一样,只不过一个是用唯一标识字符串来比较,一个是用最后一次修改的时间来比较。也是第一次请求时,响应头(Response Headers)中会添加一个Last-Modified的字段,记录了最后一次文件修改的时间,然后再次请求时,会在请求头(Request Headers)中添加If-Modified-Since字段,值就是上一次响应时响应头中Last-Modified的值。服务器会比较Last-Modefied和Last-Modefied-Since的值。
效果如下图:(图片来源网络,侵删)
图片
我使用的是这个插件vue-content-placeholders[2],其次,骨架屏是需要自己画的,你需要把布局画好做成一个组件,在需要的页面引用,然后等数据请求回来以后隐藏掉再显示正常的页面就可以。通常仅仅在接口请求比较多的页面用到骨架屏,毕竟当你的页面改动的时候骨架屏的页面布局也需要改动,如果每个页面都使用骨架屏未免太浪费开发时间也增加了日后的维护成本。
图片
图片
图片
可以在标签上直接设置class和style
vue文件引入需要的骨架屏组件即可。
·······
使用方法也很简单:
const Foo = () => import('./Foo.vue')
const Bar = () => import('./Bar.vue')
const router = new VueRouter({
routes: [{
path: '/foo',
component: Foo
},{
path: '/bar',
component: Bar
}]
})
我使用的是Vue.js官网介绍使用的prerender-spa-plugin。这个插件的配置很多,它的GitHub地址在这里[5]。
Vue.js的官网中给到该预渲染插件如下描述:
图片
预渲染也有一些需要注意的点:
npm i prerender-spa-plugin -D
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'
})
}),
]
})
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标签中有其他标签内容。倘若我们不使用预渲染,打包出的文件里,这个标签里是空的不会有任何标签。
图片
图片
具体使用可参考Vue的官方文档
Service workers可以很好的解决离线时候的首页加载问题。但是鉴于文章长度限制,就在此处不细说了。
某乎有位大佬说的很详细,链接在此[6]。
主要加载较晚才会用到的资源,告知浏览器后,浏览器就会在闲时去加载对应的资源,很适合在懒加载时使用。
对于使用prefetch获取资源,其优先级默认为最低Lowest,可以认为当浏览器空闲的时候才会去获取的资源。
图片
主要用来加载当前页面很重要的资源。
浏览器通过as值能得知资源类型,还能根据as的值发送适当的Accept头部信息。甚至可以通过as来标识他们请求资源的优先级(比如说使用as="style"属性将获得最高的优先级,即使资源不是样式文件)
图片
关于Vue中的一些优化(并非首屏优化),我推荐黄轶老师的这篇文章《揭秘 Vue.js 九个性能优化技巧》[7]。
在我个人理解看来,性能优化的最终目的并不是完全追求时间上的长短,核心目的是给用户更好的体验,在提升了帧数的情况舍弃一点点加载或者渲染时间在整体体验上要比完全追求数值上的长短有意义的多。也就是上面文章中这位朋友分享的观点:
图片