Vue應用加載過程
我們先來看看vue的入口文件index.html裏面的內容,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
從圖裏看到vue頁面從打開到加載完成是有一段白屏的時間的,那白屏的加載體驗對於首次訪問的用户來説是難以接受的,我們可以使用尺寸穩定的骨架屏,來輔助實現真實模塊佔位和瞬間加載。
vue應用從打開到頁面展示完整大致經歷了一下幾步
1.先渲染index.html的內容
2.動態插入chunk-vendors.js(第三方庫)
3.動態插入app.js(業務代碼)
4.渲染路由對應的頁面
只有當app.js文件渲染完成之後,當前路由對應的vue才會被渲染到頁面裏面,而app.js是最後才被插入到index.html裏的,因此會導致在app.js未加載之前會有一段空白的顯示。
這會引出了一個用户體驗的問題:用户看到頁面內容之前的空白時間過長,這樣頁面流失率會大大的增加,這也是我們不願意看到的。
據統計:加載超過5秒就會有74%的用户離開頁面。
骨架屏是什麼
在頁面完全渲染完成之前,用户會看到一個樣式簡單,描繪了當前頁面的大致框架,感知到頁面正在逐步加載,最終骨架屏中各個佔位部分被完全替換,體驗良好。常用於文章列表、動態列表頁等相對比較規則的列表頁面。
很多項目中都有應用: 餓了麼h5版本,知乎,facebook等網站中都有應用。
這個過程中用户會覺得內容正在逐漸加載即將呈現,降低了用户的焦躁情緒,使得加載過程主觀上變得流暢。
骨架屏相比於傳統的loading圖會在感官上覺得內容出現的流暢而不突兀,體驗更加優良。
所以骨架屏的應用大大提高了用户體驗。
下面我們看看具體的實現方法
骨架屏實踐
從vue渲染過程動圖中我們能看到index.html是最快加載的出來的
所以骨架屏需要放到index.html裏面
在index.html將骨架屏Dom結構寫好,然後在規定好的鈎子函數裏面去移除這個骨架屏就可以實現骨架屏機制。
但是這種方式有一個問題,如果每個頁面的骨架屏的樣式都不同,我們需要手動在index.html寫好每個頁面對應的結構樣式,然後再去解析路由去顯示對應的骨架屏。這樣很麻煩,不好維護。
呢有沒有一個插件幫我們去幹兩件事情:
1.將骨架屏vue文件提前渲染到index.html裏面
2.渲染路由對應的骨架屏
這樣一來就大大解放了雙手,代碼維護性也大大提高了。
vue-skeleton-webpack-plugin
github地址:https://github.com/lavas-proj...
這是一個基於 Vue 的 webpack 插件,為單頁/多頁應用生成骨架屏 skeleton,減少白屏時間,在頁面完全渲染之前提升用户感知體驗。將骨架屏也看成路由組件,在構建時使用 Vue 服務端渲染功能,將骨架屏組件的渲染結果 HTML 片段插入 HTML 頁面模版的掛載點中,將樣式內聯到 head 標籤中。這樣等前端渲染完成時,Vue 將使用客户端混合,把掛載點中的骨架屏內容替換成真正的頁面內容。
-
插件大致實現思路
- 通過vue-server-renderer將骨架屏的Vue 實例渲染為 HTML
- Vue webpack 項目使用了 HTML Webpack Plugin生成 HTML 文件,通過該插件的html-webpack-plugin-before-html-processing 事件的回調函數插入骨架屏的內容。
插件具體實現可參考作者文章
- 實現步驟
- 新建一個骨架屏的vue文件並將骨架屏的樣式寫好
- 在src文件加下新建一個skeleton.js文件,將剛才創建的骨架屏文件引入,註冊在components裏面,並在template裏面寫入這個組件,注意:這裏的組件一定要聲明ID值
import Vue from 'vue'
import SkeletonIndex from './components/skeleton/index'
export default new Vue({
components: {
SkeletonIndex
},
template: `
<div>
<SkeletonIndex id="skeletonIndex" style="display:none" />
</div>
`
})
3.因為我的項目是vue-cli3,所以要對vue.config.js操作,這裏需要引入skeleton.js,並且可以在routes裏面配置路由對應的骨架屏組件。注意extract: true必須要配置,否則骨架屏vue裏的樣式不會被插入到style裏面。~~~~其他配置項可以到作者github上查詢。
const path = require('path');
const SkeletonWebpackPlugin = require('vue-skeleton-webpack-plugin');
module.exports = {
css: {
extract: true
},
configureWebpack: {
plugins: [
new SkeletonWebpackPlugin({
webpackConfig: {
entry: {
app: path.join(__dirname, './src/skeleton.js'),
},
},
router: {
mode: process.env.NODE_ENV === 'development' ? 'hash' : 'history',
routes: [
{ path: /.+/, skeletonId: 'skeletonIndex' },
]
}
}),
],
},
};
配置完成重啓應用看一下效果
實際應用遇到的問題
由於這個插件使用的服務端渲染,所以當app.$mount掛載成功後,骨架屏的內容會被替換掉。
當我們的業務代碼慢慢變多後,頁面dom的繪製會變慢,所以會出現骨架屏消失後會有短暫白屏的情況。
解決方案:
因為我發現在app.vue裏面註冊的全局組件會比<router-view></router-view>裏的提前渲染,所以我在app.vue裏面註冊一個骨架屏的全局變量,然後在router.beforeEach裏面去根據路由動態渲染對應的骨架屏。
// app.vue
<template>
<div id="app">
<router-view></router-view>
<Skeleton />
</div>
</template>
<script>
import Skeleton from './components/app/skeleton/index'
export default {
components: {
Skeleton
}
}
</script>
// ./components/app/skeleton/index.vue
<template>
<div v-if="skeletonName" class="skeleton-wrapper">
<component :is="skeletonName"></component>
</div>
</template>
<script>
import { mapState } from 'vuex'
import allTop from './allTop'
import bless_sort from './bless_sort'
import classify_singer from './classify_singer'
import home from './home'
import loading from './loading'
import mv from './mv'
import photo from './photo'
import photov2 from './photov2'
import send_video from './send_video'
export default {
computed: {
...mapState({
skeletonName: state => state.skeletonName
})
},
components: {
allTop,
bless_sort,
classify_singer,
home,
loading,
mv,
photo,
photov2,
send_video
}
}
</script>
// main.js
router.beforeEach((to, from, next) => {
store.commit('SAVE_TOGGLE_SKELETON', false)
if (util.skeletonCheckRoute(to.path)) {
store.commit('SAVE_TOGGLE_SKELETON', util.skeletonCheckRoute(to.path, 'getPath'))
}
next()
}
這樣就解決了短暫白屏的問題,如果有更好的方法可以提出來。
總結
單應用的一個最大的問題就是首屏加載東西太多,加載時間過長。
而骨架屏的應用只是降低了用户焦慮,我們還是要優化代碼體積。
- 按需加載
- 異步組件
- webpack-bundle-analyzer
- ...
繪製骨架屏插件
vue-content-loader
在線繪製骨架屏網站:https://skeletonreact.com/
Vue Content Loader是一個基於Vue.js的SVG佔位符加載,可自定義的SVG組件,用於創建佔位符加載,例如Facebook加載卡。Vue Content Loader是react-content-loader的Vue實現。