Vue頁(yè)面骨架屏的實(shí)現(xiàn)方法
在開(kāi)發(fā)webapp的時(shí)候總是會(huì)受到首屏加載時(shí)間過(guò)長(zhǎng)的影響,主流的解決方法是在載入完成之前顯示loading圖效果,而一些大公司會(huì)配置一套服務(wù)端渲染的架構(gòu)來(lái)解決這個(gè)問(wèn)題??紤]到ssr所要解決的一系列問(wèn)題,越來(lái)越多的APP采用了“骨架屏”的方式去提升用戶(hù)體驗(yàn)。
小米商城:

一、分析Vue頁(yè)面的內(nèi)容加載過(guò)程
vue項(xiàng)目中的入口index.html只有簡(jiǎn)單的內(nèi)容:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>Document</title> </head> <body> <div id="root"> </div> <script type="text/javascript" src="bundle.js"></script></body> </body> </html>
當(dāng)js執(zhí)行完之后,會(huì)用vue渲染成的dom將div#root完全替換掉。
我們?cè)?code>div#root中加入模擬骨架屏,在Chrome開(kāi)發(fā)者工具調(diào)整網(wǎng)速:
<div id="root"> 這里是骨架屏 </div>

由此可知,將骨架屏內(nèi)容直接插入div#root中即可實(shí)現(xiàn)骨架屏。
二、使用vue-server-renderer來(lái)實(shí)現(xiàn)骨架屏
我們需要骨架屏也是一個(gè)單獨(dú)的.vue文件,因此我們需要用到vue-server-renderer。對(duì)vue服務(wù)端渲染有所了解的同學(xué)一定知道,這個(gè)插件能夠?qū)ue項(xiàng)目在node端打包成一個(gè)bundle,然后由bundle生成對(duì)應(yīng)的html。
首先是生成項(xiàng)目:
. ├── build │ ├── webpack.config.client.js │ └── webpack.config.server.js ├── src │ └── views │ ├── index │ │ └── index.vue │ ├── skeleton │ │ └── skeleton.vue │ ├── app.vue │ ├── index.js │ └── skeleton-entry.js ├── index.html └── skeleton.js └── package.json
vue的服務(wù)端渲染一般會(huì)用vue-server-renderer將整個(gè)項(xiàng)目在node端打包成一份bundle,而這里我們只要一份有骨架屏的html,所以會(huì)有一個(gè)單獨(dú)的骨架屏入口文件skeleton-entry.js,一個(gè)骨架屏打包webpack配置webpack.config.server.js,而skeleton.js作用是將webpack打包出來(lái)的bundle寫(xiě)入到index.html中。
//skeleton-entry.js
import Vue from 'vue'
import Skeleton from './views/skeleton/skeleton.vue'
export default new Vue({
components: {
Skeleton
},
template: '<skeleton />'
})
//webpack.config.server.js
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
module.exports = {
mode: process.env.NODE_ENV,
target: 'node',
entry: path.join(__dirname, '../src/skeleton-entry.js'),
output: {
path: path.join(__dirname, '../server-dist'),
filename: 'server.bundle.js',
libraryTarget: 'commonjs2'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
externals: Object.keys(require('../package.json').dependencies),
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
plugins: [
new VueLoaderPlugin(),
new VueSSRServerPlugin({
filename: 'skeleton.json'
})
]
}
其中骨架屏的webpack配置因?yàn)槭莕ode端,所以需要target: 'node' libraryTarget: 'commonjs2'。在VueSSRServerPlugin中,指定了其輸出的json文件名。當(dāng)執(zhí)行webpack會(huì)在/server-dist目錄下生成一個(gè)skeleton.json文件,這個(gè)文件記載了骨架屏的內(nèi)容和樣式,會(huì)提供給vue-server-renderer使用。
//skeleton.js
const fs = require('fs')
const path = require('path')
const createBundleRenderer = require('vue-server-renderer').createBundleRenderer
// 讀取`skeleton.json`,以`index.html`為模板寫(xiě)入內(nèi)容
const renderer = createBundleRenderer(path.join(__dirname, './server-dist/skeleton.json'), {
template: fs.readFileSync(path.join(__dirname, './index.html'), 'utf-8')
})
// 把上一步模板完成的內(nèi)容寫(xiě)入(替換)`index.html`
renderer.renderToString({}, (err, html) => {
fs.writeFileSync('index.html', html, 'utf-8')
})
注意,作為模板的html文件,需要在被寫(xiě)入內(nèi)容的位置添加<!--vue-ssr-outlet-->占位符,本例子在div#root里寫(xiě)入:
<div id="root"> <!--vue-ssr-outlet--> </div>
最后執(zhí)行node skeleton就能實(shí)現(xiàn)vue的骨架屏。
最終的index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Document</title>
<style data-vue-ssr-id="a7049cb4:0">
.skeleton[data-v-61761ff8] {
position: relative;
height: 100%;
overflow: hidden;
padding: 15px;
box-sizing: border-box;
background: #fff;
}
.skeleton-nav[data-v-61761ff8] {
height: 45px;
background: #eee;
margin-bottom: 15px;
}
.skeleton-swiper[data-v-61761ff8] {
height: 160px;
background: #eee;
margin-bottom: 15px;
}
.skeleton-tabs[data-v-61761ff8] {
list-style: none;
padding: 0;
margin: 0 -15px;
display: flex;
flex-wrap: wrap;
}
.skeleton-tabs-item[data-v-61761ff8] {
width: 25%;
height: 55px;
box-sizing: border-box;
text-align: center;
margin-bottom: 15px;
}
.skeleton-tabs-item span[data-v-61761ff8] {
display: inline-block;
width: 55px;
height: 55px;
border-radius: 55px;
background: #eee;
}
.skeleton-banner[data-v-61761ff8] {
height: 60px;
background: #eee;
margin-bottom: 15px;
}
.skeleton-productions[data-v-61761ff8] {
height: 20px;
margin-bottom: 15px;
background: #eee;
}
</style></head>
<body>
<div id="root">
<div data-server-rendered="true" class="skeleton page" data-v-61761ff8><div class="skeleton-nav" data-v-61761ff8></div> <div class="skeleton-swiper" data-v-61761ff8></div> <ul class="skeleton-tabs" data-v-61761ff8><li class="skeleton-tabs-item" data-v-61761ff8><span data-v-61761ff8></span></li><li class="skeleton-tabs-item" data-v-61761ff8><span data-v-61761ff8></span></li><li class="skeleton-tabs-item" data-v-61761ff8><span data-v-61761ff8></span></li><li class="skeleton-tabs-item" data-v-61761ff8><span data-v-61761ff8></span></li><li class="skeleton-tabs-item" data-v-61761ff8><span data-v-61761ff8></span></li><li class="skeleton-tabs-item" data-v-61761ff8><span data-v-61761ff8></span></li><li class="skeleton-tabs-item" data-v-61761ff8><span data-v-61761ff8></span></li><li class="skeleton-tabs-item" data-v-61761ff8><span data-v-61761ff8></span></li></ul> <div class="skeleton-banner" data-v-61761ff8></div> <div class="skeleton-productions" data-v-61761ff8></div><div class="skeleton-productions" data-v-61761ff8></div><div class="skeleton-productions" data-v-61761ff8></div><div class="skeleton-productions" data-v-61761ff8></div><div class="skeleton-productions" data-v-61761ff8></div><div class="skeleton-productions" data-v-61761ff8></div></div>
</div>
</body>
</html>
看下效果:

效果還是闊以的。
尾聲
文章開(kāi)頭小米商城手機(jī)頁(yè)面就是用的這樣的方法,不同的是它的骨架屏是一個(gè)base64的圖片。
更多關(guān)于vue-server-renderer內(nèi)容請(qǐng)戳vue-ssr
文章相關(guān)代碼已經(jīng)同步到Github,歡迎查閱~
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Element實(shí)現(xiàn)動(dòng)態(tài)增加多個(gè)輸入框并校驗(yàn)
本文主要介紹了Element實(shí)現(xiàn)動(dòng)態(tài)增加多個(gè)輸入框并校驗(yàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Vue生態(tài)系統(tǒng)工具庫(kù)Vueuse的使用示例詳解
Vueuse 是一個(gè)功能強(qiáng)大的 Vue.js 生態(tài)系統(tǒng)工具庫(kù),它提供了一系列的可重用的 Vue 組件和函數(shù),本文將介紹 Vueuse 的主要特點(diǎn)和用法,以及它在 Vue.js 開(kāi)發(fā)中的作用和優(yōu)勢(shì),感興趣的可以了解下2024-02-02
vue全局掛載實(shí)現(xiàn)APP全局彈窗的示例代碼
本文主要介紹了vue全局掛載實(shí)現(xiàn)APP全局彈窗的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
webpack dev-server代理websocket問(wèn)題
這篇文章主要介紹了webpack dev-server代理websocket問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Vue MVVM模型與data及methods屬性超詳細(xì)講解
MVVM旨在利用WPF中的數(shù)據(jù)綁定函數(shù),通過(guò)從視圖層中幾乎刪除所有GUI代碼(代碼隱藏),更好地促進(jìn)視圖層開(kāi)發(fā)與模式其余部分的分離,這篇文章主要介紹了Vue MVVM模型與data及methods屬性2022-10-10
Vue實(shí)現(xiàn)倒計(jì)時(shí)小功能
這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)倒計(jì)時(shí)小功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Vue組件tree實(shí)現(xiàn)樹(shù)形菜單
這篇文章主要為大家詳細(xì)介紹了Vue組件tree實(shí)現(xiàn)樹(shù)形菜單,小巧實(shí)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04

