天天看點

Vue頁面骨架屏尾聲

在開發webapp的時候總是會受到首屏加載時間過長的影響,主流的解決方法是在載入完成之前顯示loading圖效果,而一些大公司會配置一套服務端渲染的架構來解決這個問題。考慮到ssr所要解決的一系列問題,越來越多的APP采用了“骨架屏”的方式去提升使用者體驗。

小米商城:

Vue頁面骨架屏尾聲

一、分析Vue頁面的内容加載過程

vue項目中的入口index.html隻有簡單的内容:

<!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>           

當js執行完之後,會用vue渲染成的dom将

div#root

完全替換掉。

我們在

div#root

中加入模拟骨架屏,在Chrome開發者工具調整網速:

<div id="root">
    這裡是骨架屏
</div>           
Vue頁面骨架屏尾聲

由此可知,将骨架屏内容直接插入

div#root

中即可實作骨架屏。

二、使用vue-server-renderer來實作骨架屏

我們需要骨架屏也是一個單獨的

.vue

檔案,是以我們需要用到

vue-server-renderer

。對vue服務端渲染有所了解的同學一定知道,這個插件能夠将vue項目在node端打包成一個bundle,然後由bundle生成對應的html。

首先是生成項目:

.
├── 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的服務端渲染一般會用

vue-server-renderer

将整個項目在node端打包成一份bundle,而這裡我們隻要一份有骨架屏的html,是以會有一個單獨的骨架屏入口檔案

skeleton-entry.js

,一個骨架屏打包webpack配置

webpack.config.server.js

,而

skeleton.js

作用是将webpack打包出來的bundle寫入到

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配置因為是node端,是以需要

target: 'node'

libraryTarget: 'commonjs2'

。在

VueSSRServerPlugin

中,指定了其輸出的json檔案名。當執行webpack會在/server-dist目錄下生成一個

skeleton.json

檔案,這個檔案記載了骨架屏的内容和樣式,會提供給

vue-server-renderer

使用。

//skeleton.js
const fs = require('fs')
const path = require('path')

const createBundleRenderer = require('vue-server-renderer').createBundleRenderer

// 讀取`skeleton.json`,以`index.html`為模闆寫入内容
const renderer = createBundleRenderer(path.join(__dirname, './server-dist/skeleton.json'), {
  template: fs.readFileSync(path.join(__dirname, './index.html'), 'utf-8')
})

// 把上一步模闆完成的内容寫入(替換)`index.html`
renderer.renderToString({}, (err, html) => {
  fs.writeFileSync('index.html', html, 'utf-8')
})           
注意,作為模闆的html檔案,需要在被寫入内容的位置添加<!--vue-ssr-outlet-->占位符,本例子在div#root裡寫入:

<div id="root">
 <!--vue-ssr-outlet-->
</div>           

最後執行

node skeleton

就能實作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>           

看下效果:

Vue頁面骨架屏尾聲

效果還是闊以的。

尾聲

文章開頭小米商城手機頁面就是用的這樣的方法,不同的是它的骨架屏是一個base64的圖檔。

更多關于

vue-server-renderer

内容請戳

vue-ssr

原文釋出時間為:2018年05月23日

原文作者:掘金

本文來源: 

掘金 https://juejin.im/entry/5b3a29f95188256228041f46

如需轉載請聯系原作者

繼續閱讀