路由跳转前的骨架屏实现

2,459 阅读5分钟

一、前言

(注意:本文是针对路由懒加载的项目)

(1)骨架屏相信大多数人都听讲过,也实现过。(还不清楚骨架屏是什么的可以参考 开发一个简单易懂的webpack骨架屏插件

但一般情况下都是给首页第一次加载的时候添加骨架屏,而不是给每个路由页面都加上骨架屏。

怎么说呢?

单页面应用第一次加载的时候需要加载大量的js,css资源,如果网络慢的情况,首次打开页面可能会出现十几秒的白屏时间。 现在市场上都是针对首次加载做了骨架屏优化,很少有做路由跳转到其他页面的骨架屏优化。(可能很多人都认为没必要做其他路由页面的骨架屏优化,但本文先忽略这个必要性)

(2)看了vue-skeleton-webpack-plugin的源码,发现该插件的routers也做不了其他路由页面的骨架屏优化。所以想出了本文的方案。

二、实现原理

(1)路由懒加载

市场上的单页面应用多数都会做路由懒加载,以vue项目为例: 在注册vue-router的时候,一般像下图做路由分割

大概意思就是,当跳转到 /home 页面的时候才加载/home页面对应的js资源。只有当这个js资源加载完成之后页面才会更新。所以如果加载这个js资源的时间用了10几秒,那么我们将会原地等待10几秒,或者会白屏10几秒。这样的用户体验是非常不好的。

如果我们可以在这10几秒里做一个骨架屏让它先展示出来,等到js资源加载回来后再隐藏它。这样的用户体验不是更加好呢。

(2)实现原理:

如上图,component对应的value是一个函数,该函数就是加载js资源的Promise, 众所周知,Promise有then方法可以知道js资源什么时候加载完成。那么我们可以在import('../views/home/index.vue')之前先让骨架屏显示出来,让<div id="app"></div>隐藏。然后在import('../views/home/index.vue').then()方法里再将骨架屏隐藏,<div id="app"></div>显示。

(2.1)首先对index.html做修改,如下:

在index.html里添加两个同级得div,一个是骨架屏得div,一个是<div id="app"></div>,你可以在骨架屏得div里写上自己的样式。再在<head>里写上简单的显示隐藏的css样式

<!DOCTYPE html>
<html lang="en">
  <head>    
    <meta charset="UTF-8">    
    <meta name="viewport" content="width=device-width, initial-scale=1.0">    
    <meta http-equiv="X-UA-Compatible" content="ie=edge">    
    <title>Document</title>
    <style>
      .skeleton-block[data-v-07e00dc6] {  
        display: flex;  
        flex-direction: column;  
        padding: 16px;
      }
      .body-wrap-show .skeleton-wrap {
        display: block !important;
      }
      .body-wrap-show #app {
        display: none !important;
      }
      .body-wrap-hidden .skeleton-wrap {
        display: none !important;
      }
      .body-wrap-hidden #app {
        display: block !important;
      }
    </style>
  </head>
  <body>    
    <div data-server-rendered="true" class="skeleton-wrap" style="display: none;">
      <div data-v-07e00dc6>
        <section class="skeleton-block" data-v-07e00dc6>
          <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTA4MCAyNjEiPjxkZWZzPjxwYXRoIGlkPSJiIiBkPSJNMCAwaDEwODB2MjYwSDB6Ii8+PGZpbHRlciBpZD0iYSIgd2lkdGg9IjIwMCUiIGhlaWdodD0iMjAwJSIgeD0iLTUwJSIgeT0iLTUwJSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94Ij48ZmVPZmZzZXQgZHk9Ii0xIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlQ29sb3JNYXRyaXggaW49InNoYWRvd09mZnNldE91dGVyMSIgdmFsdWVzPSIwIDAgMCAwIDAuOTMzMzMzMzMzIDAgMCAwIDAgMC45MzMzMzMzMzMgMCAwIDAgMCAwLjkzMzMzMzMzMyAwIDAgMCAxIDAiLz48L2ZpbHRlcj48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDEpIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGw9IiNGRkYiIHhsaW5rOmhyZWY9IiNiIi8+PHBhdGggZmlsbD0iI0Y2RjZGNiIgZD0iTTIzMCA0NGg1MzN2NDZIMjMweiIvPjxyZWN0IHdpZHRoPSIxNzIiIGhlaWdodD0iMTcyIiB4PSIzMCIgeT0iNDQiIGZpbGw9IiNGNkY2RjYiIHJ4PSI0Ii8+PHBhdGggZmlsbD0iI0Y2RjZGNiIgZD0iTTIzMCAxMThoMzY5djMwSDIzMHpNMjMwIDE4MmgzMjN2MzBIMjMwek04MTIgMTE1aDIzOHYzOUg4MTJ6TTgwOCAxODRoMjQydjMwSDgwOHpNOTE3IDQ4aDEzM3YzN0g5MTd6Ii8+PC9nPjwvc3ZnPg==" data-v-07e00dc6> 
          <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTA4MCAyNjEiPjxkZWZzPjxwYXRoIGlkPSJiIiBkPSJNMCAwaDEwODB2MjYwSDB6Ii8+PGZpbHRlciBpZD0iYSIgd2lkdGg9IjIwMCUiIGhlaWdodD0iMjAwJSIgeD0iLTUwJSIgeT0iLTUwJSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94Ij48ZmVPZmZzZXQgZHk9Ii0xIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlQ29sb3JNYXRyaXggaW49InNoYWRvd09mZnNldE91dGVyMSIgdmFsdWVzPSIwIDAgMCAwIDAuOTMzMzMzMzMzIDAgMCAwIDAgMC45MzMzMzMzMzMgMCAwIDAgMCAwLjkzMzMzMzMzMyAwIDAgMCAxIDAiLz48L2ZpbHRlcj48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDEpIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGw9IiNGRkYiIHhsaW5rOmhyZWY9IiNiIi8+PHBhdGggZmlsbD0iI0Y2RjZGNiIgZD0iTTIzMCA0NGg1MzN2NDZIMjMweiIvPjxyZWN0IHdpZHRoPSIxNzIiIGhlaWdodD0iMTcyIiB4PSIzMCIgeT0iNDQiIGZpbGw9IiNGNkY2RjYiIHJ4PSI0Ii8+PHBhdGggZmlsbD0iI0Y2RjZGNiIgZD0iTTIzMCAxMThoMzY5djMwSDIzMHpNMjMwIDE4MmgzMjN2MzBIMjMwek04MTIgMTE1aDIzOHYzOUg4MTJ6TTgwOCAxODRoMjQydjMwSDgwOHpNOTE3IDQ4aDEzM3YzN0g5MTd6Ii8+PC9nPjwvc3ZnPg==" data-v-07e00dc6>
        </section>
      </div>
    </div>
    <div id="app"></div>    
    </script>
    </body>
    </html

(2.2)路由注册文件的修改,如下

大概意思是:每次路由跳转之前先将<div id="app"></div>隐藏,让骨架怕展示。在then方法里再让<div id="app"></div>展示,骨架屏隐藏。这样每次路由跳转都会出现骨架屏了。

import Vue from 'vue';
import vueRouter from 'vue-router';
Vue.use(vueRouter);

const router = new vueRouter({
  mode: 'history',
  routes: [
    {
      path: '/home',
      component: () => {
        let body = document.body;
        // 每次路由跳转之前先将<div id="app"></div>隐藏
        document.getElementById('app').style.display = 'none';
        // 移除body上的body-wrap-hidden
        body.classList.remove('body-wrap-hidden');
        // 在body上的添加class 为body-wrap-show
        body.classList.add('body-wrap-show');

        return import('../views/home/index.vue').then(res => {
            //当js资源加载完成后,将骨架屏隐藏
            body.classList.remove('body-wrap-show');
            //当js资源加载完成后,将<div id="app"></div>展示
            body.classList.add('body-wrap-hidden');
            //释放body节点的引用
            body = null;
            // 注意:then方法里面必须得返回一个值,不然vue-router会报错
            return res;
        })
      }
    },
  ]
});

export default router;

(2.4)效果如下:

留意上图,当我点击“我是首页”的时候,是立刻出现骨架屏的,再看右边的js资源是正在加载,还没有加载完成。也就是说,它不是使用v-if或者v-show在“我是详情页面”的页面里面做的骨架屏。后者它有个限制,因为后者所谓的骨架屏也是属于“我是详情页面”的代码,必须等待js资源返回后才会显示。而前者则是加载js资源之前就已经显示了。 **

(2.3)结束语:

本文只是个思路,真正想做到每个路由页面都有各自的骨架屏还得做很多的事情。 (有不当之处还望指出)。