• Vue页面骨架屏(一)


    在开发webapp的时候总是会受到首屏加载时间过长的影响,主流的解决方法是在载入完成之前显示loading图效果,而一些大公司会配置一套服务端渲染的架构来解决这个问题。考虑到ssr所要解决的一系列问题,越来越多的APP采用了“骨架屏”的方式去提升用户体验。

    一、分析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>

    由此可知,将骨架屏内容直接插入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] {
       25%;
      height: 55px;
      box-sizing: border-box;
      text-align: center;
      margin-bottom: 15px;
    }
    .skeleton-tabs-item span[data-v-61761ff8] {
      display: inline-block;
       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>

    尾声

    文章开头小米商城手机页面就是用的这样的方法,不同的是它的骨架屏是一个base64的图片。

    更多关于vue-server-renderer内容请戳vue-ssr
    相关demo
    转载自https://segmentfault.com/a/1190000014963269

  • 相关阅读:
    在响应式项目中连接设计与开发
    社交APP的痛点及九大流派解析,微信陌陌,咱还约吗
    网传奶茶妹将进入红杉资本
    OLE、OCX和ActiveX控件之间的比较
    Unity3D网络游戏实战(第2版)
    离婚?在Facebook上把已婚状态改为单身就可以!
    同样酷炫但却失败了的谷歌眼镜,能否给Apple Watch一些前车之鉴?
    Apple Watch首批评测放出:有吐槽、有体贴……毒哭了,暖哭了
    如何培养战略领导力,赢得“长久游戏”?
    博客社交已死,数据社交“永生”
  • 原文地址:https://www.cnblogs.com/hongsusu/p/9439152.html
Copyright © 2020-2023  润新知