• Vue单页面骨架屏实践


    github 地址: VV-UI/VV-UI

    演示地址: vv-ui

    文档地址:skeleton

    关于骨架屏介绍

    骨架屏的作用主要是在网络请求较慢时,提供基础占位,当数据加载完成,恢复数据展示。这样给用户一种很自然的过渡,不会造成页面长时间白屏或者闪烁等情况。 常见的骨架屏实现方案有ssr服务端渲染和prerender两种解决方案。这里主要通过代码为大家展示如何一步步做出这样一个骨架屏:

    prerender 渲染骨架屏

    本组件库骨架屏的实现也是基于预渲染去实现的,有关于预渲染更详细的介绍请参考这篇文章:处理 Vue 单页面 Meta SEO的另一种思路 下面我们主要介绍其实现步骤,首先我们也是需要配置webpack-plugin,不过已经有实现好的prerender-spa-plugin可用

    var path = require('path')
    var PrerenderSpaPlugin = require('prerender-spa-plugin')
    
    module.exports = {
      // ...
      plugins: [
        new PrerenderSpaPlugin(
          // Absolute path to compiled SPA
          path.join(__dirname, '../dist'),
          // List of routes to prerender
          ['/']
        )
      ]
    }
    

      

    然后写好我们的骨架屏文件main.skeleton.vue

    <template>
      <div class="main-skeleton">
        <w-skeleton height="80px"></w-skeleton>
        <div>
          <div class="skeleton-container">
            <div class="skeleton">
              <w-skeleton height="300px"></w-skeleton>
            </div>
            <w-skeleton height="45px"></w-skeleton>
          </div>
          <div class="skeleton-bottom">
            <w-skeleton height="45px"></w-skeleton>
          </div>
        </div>
      </div>
    </template>
    

      

    当初次进入页面的时候我们需要显示骨架屏,数据加载完,我们需要移除骨架屏:

    <template>
      <div id="app">
        <mainSkeleton v-if="!init"></mainSkeleton>
        <div v-else>
          <div class="body"></div>
        </div>
      </div>
    </template>
    <script>
     import mainSkeleton from './main.skeleton.vue'
    
      export default {
        name: 'app',
        data () {
          return {
            init: false
          }
        },
        mounted () {
          //  这里模拟数据请求
          setTimeout(() => {
            this.init = true
          }, 250)
        },
        components: {
          mainSkeleton
        }
      }
    </script>
    

     

    ssr 渲染骨架屏

    下面我用我灵魂画师的笔法,画出了大致的过程:

    首先创建我们的skeleton.entry.js

    import Vue from 'vue';
    import Skeleton from './skeleton.vue';
    
    export default new Vue({
        components: {
            Skeleton
        },
        template: '<skeleton />'
    }); 
    

      

    当然这里的skeleton.vue使我们事先写好的骨架屏组件,看起来可能是这样:

     <template>
        <div class="skeleton-wrapper">
            <header class="skeleton-header"></header>
            <div class="skeleton-block"></div>
        </div>
    </template>
    

      

    然后我们需要的是能把skeleton.entry.js编译成服务端渲染可用的bundle文件,所以我们需要有个编译骨架屏的webpack.ssr.conf.js文件:

    const path = require('path');
    const merge = require('webpack-merge');
    const baseWebpackConfig = require('./webpack.base.conf');
    const nodeExternals = require('webpack-node-externals');
    
    function resolve(dir) {
        return path.join(__dirname, dir);
    }
    
    module.exports = merge(baseWebpackConfig, {
        target: 'node',
        devtool: false,
        entry: {
            app: resolve('./src/skeleton.entry.js')
        },
        output: Object.assign({}, baseWebpackConfig.output, {
            libraryTarget: 'commonjs2'
        }),
        externals: nodeExternals({
            whitelist: /.css$/
        }),
        plugins: []
    });
    

      

    接下来最终的步骤,就是编写我们的webpackPlugin,我们期望我们的webpackPlugin可以帮我们把入口文件编译成bundle,然后再通过vue-server-renderer来render bundle,最终产出响应的html片段和css片段,这里贴出核心代码:

    // webpack start to work
        var serverCompiler = webpack(serverWebpackConfig);
        var mfs = new MFS();
        // output to mfs
        serverCompiler.outputFileSystem = mfs;
        serverCompiler.watch({}, function (err, stats) {
    
            if (err) {
                reject(err);
                return;
            }
    
            stats = stats.toJson();
            stats.errors.forEach(function (err) {
                console.error(err);
            });
            stats.warnings.forEach(function (err) {
                console.warn(err);
            });
    
            var bundle = mfs.readFileSync(outputPath, 'utf-8');
            var skeletonCss = mfs.readFileSync(outputCssPath, 'utf-8');
            // create renderer with bundle
            var renderer = createBundleRenderer(bundle);
            // use vue ssr to render skeleton
            renderer.renderToString({}, function (err, skeletonHtml) {
                if (err) {
                    reject(err);
                }
                else {
                    resolve({skeletonHtml: skeletonHtml, skeletonCss: skeletonCss});
                }
            });
        });
    

      

    最后一步,我们对产出的html片段, css片段进行组装,产出最终的html,所以我们需要监听webpack 的编译挂载之前的事件:

    compiler.plugin('compilation', function (compilation) {
    
        // add listener for html-webpack-plugin
        compilation.plugin('html-webpack-plugin-before-html-processing', function (htmlPluginData, callback) {
            ssr(webpackConfig).then(function (ref) {
                var skeletonHtml = ref.skeletonHtml;
                var skeletonCss = ref.skeletonCss;
    
                // insert inlined styles into html
                var headTagEndPos = htmlPluginData.html.lastIndexOf('</head>');
                htmlPluginData.html = insertAt(htmlPluginData.html, ("<style>" + skeletonCss + "</style>"), headTagEndPos);
    
                // replace mounted point with ssr result in html
                var appPos = htmlPluginData.html.lastIndexOf(insertAfter) + insertAfter.length;
                htmlPluginData.html = insertAt(htmlPluginData.html, skeletonHtml, appPos);
                callback(null, htmlPluginData);
            });
        });
     });
    

      

    关于:

    作者:monkeyWang

    本文参考文章:为vue项目添加骨架屏

    本文源码详见:VV-UI/VV-UI

    本人主页:monkeyWang

    微信公众号:前端知识铺

    会不定期推送前端技术文章,欢迎关注

  • 相关阅读:

    二分查找法
    LeetCode-Two Sum III
    LeetCode-Add and Search Word
    LeetCode-Longest Substring with At Least K Repeating Characters
    LeetCode-Rearrange String k Distance Apart
    LeetCode-Game of Life
    LeetCode-Walls and Gates
    LeetCode-Water and Jug Problem
    LeetCode-Inorder Successor in BST
  • 原文地址:https://www.cnblogs.com/tiedaweishao/p/8032337.html
Copyright © 2020-2023  润新知