骨架屏效果

骨架屏效果

在我们访问页面时,当资源未加载完成时,通常会出现空白,这种体验不是很好,常用的解决方法有添加loading页、使用骨架屏,相比于loading页,骨架屏更贴近实际内容。如下图所示:

在这里插入图片描述
刷新页面时资源未加载成功时,先展示骨架屏。

骨架屏原理

在使用vue框架生成的项目中,当我们访问页面时,最先加载的是一个名为index.html文件,此时它里面就只有一个id为app的div元素,只有当js资源加载完成后,vue框架才会生成页面展示需要的DOM节点并注入到页面中进行显示,js资源加载时间的长短就决定了空白时长,如果在这段时间index.html内含有内容,那么用户看到的就不再是空白,而骨架屏效果就是利用这一原理,将页面的内容占位情况在用户访问之前就写入到index.html中,这样在js资源未加载成功时,用户看到的就是页面内容占位情况。这其中需要用到服务端渲染知识,在服务端将含有骨架屏内容的页面生成好,浏览器只负责展示。vue项目服务端渲染我们使用官方比较推荐的vue-server-renderer

在vue项目中如何引入骨架屏

以使用vue-cli3默认方式创建的vue项目为例,介绍骨架屏的引入:

骨架屏的实现方式有多种,可以简单使用一张图片代替,也可以写一个vue组件,也可以引用一些工具在打包时自动生成,本文是手动写了一个骨架屏vue组件。

首先根据页面内容编写对应的骨架屏组件,如下:

// HelloWorldSkeleton.vue
<template>
  <div class="skeleton-wrapper">
    <div class="img-placeholder"></div>
    <h1 class="h1-placeholder"></h1>
    <p class="p-placeholder"></p>
    <h3 class="h3-placeholder"></h3>
    <ul class="ul-container">
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
    </ul>
    <h3 class="h3-placeholder"></h3>
    <ul class="ul-container">
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
    </ul>
    <h3 class="h3-placeholder"></h3>
    <ul class="ul-container">
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
      <li class="li-placeholder"></li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'hello-world-skeleton'
}
</script>

<style lang="scss" scoped>
.skeleton-wrapper{
  margin-top: 60px;
  text-align: center;
  .img-placeholder{
    display: inline-block;
    width: 200px;
    height: 200px;
    background: #e5e5e5;
  }
  .h1-placeholder, .p-placeholder{
    width: 400px;
    height: 37px;
    margin: 0 auto;
    background: #e5e5e5;
  }
  .p-placeholder{
    margin: 16px auto;
  }
  .h3-placeholder{
    width: 200px;
    height: 22px;
    margin: 0 auto;
    background: #e5e5e5;
  }
  .ul-container{
    list-style-type: none;
    padding: 0;
  }
  .li-placeholder{
    display: inline-block;
    width: 80px;
    height: 18px;
    margin: 0 10px;
    background: #e5e5e5;
  }
}
</style>

骨架屏编写完成后,接下来就是如何引用的问题了。vue-cli3创建的项目主要是用于客户端渲染,而骨架屏需要用到服务端渲染,即同一项目出现两种渲染方式,这就需要我们对项目的打包配置进行修改,vue-cli3对webpack的集成度比较高,有好处也有坏处,好处就是省事,帮我们免去一些基本配置,坏处就是当我们需要更改webpack配置时会不太容易且有一定限制,vue-cli3允许我们在项目根目录下创建vue.config.js配置文件,在该文件中通过配置configureWebpack来修改webpack相关配置。

vue-cli3创建的项目中自带的webpack配置是用于客户端渲染,我们可以不对客户端渲染的打包配置进行配置,只需要添加服务端渲染的相关配置即可,具体如下:

// vue.config.js
const path = require('path')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const nodeExternals = require('webpack-node-externals')
const ISSERVER = process.env.WEBPACK_TARGET === 'node'
module.exports = {
  configureWebpack: () => {
    if (ISSERVER) {
      return {
        target: 'node',
        entry: path.join(__dirname, './src/components/skeleton-entry.js'),
        devtool: 'source-map',
        output: {
          libraryTarget: 'commonjs2'
        },
        externals:nodeExternals({
          whitelist: /\.css$/
        }),
        plugins: [
          new VueSSRServerPlugin()
        ]
      }
    }
  } 
}

vue.config.js文件中通过接收变量WEBPACK_TARGET来区分是客户端渲染还是服务端渲染,如果是服务端渲染,我们需要重新配置webpack的入口文件等属性。变量WEBPACK_TARGET是如何传入的呢?当然是通过执行package.jsonscripts命令来传入的,因此需要对scripts命令进行修改:

{
	...
	"scripts": {
    	"serve:client": "vue-cli-service serve",
    	"serve:server": "npm run build:server && node skeleton.js",
    	"build:client": "vue-cli-service build",
    	"build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
    	"build": "npm run build:server && move dist\\vue-ssr-server-bundle.json bundle && npm run build:client && move bundle dist\\vue-ssr-server-bundle.json",
    	"build:mac": "npm run build:server && mv dist/vue-ssr-server-bundle.json bundle && npm run build:client && mv bundle dist/vue-ssr-server-bundle.json",
    	"lint": "vue-cli-service lint",
    	"dev": "npm run serve:server && npm run serve:client"
  	},
  	...
}

命令解读:其中,serve:client命令用于在本地启动服务(用于浏览客户端渲染的相关页面);serve:server命令中串联执行了npm run build:servernode skeleton.js命令,npm run build:server用于服务端渲内容的打包,利用vue-server-renderer/server-plugin插件最终输出一个json文件,node skeleton.js用于读取生成的json文件,并通过vue-server-renderercreateBundleRenderer方法将内容写入到index.html文件中;build:client命令用于打包客户端渲染相关的内容;build:server命令用于打包服务端渲染的相关内容,通过传入WEBPACK_TARGET=node参数,告诉webpack使用服务端渲染相关配置进行打包,如果不带--mode server参数,那么打包时会报错,如下:
在这里插入图片描述
附上地址

build命令就是将build:server命令与build:client命令结合,由于在执行vue-cli-service build命令时会删除dist文件夹,当build:server生成的json文件在dist中时继续执行build:client命令会将json文件删除,因此使用了move命令先将生成的json文件移出dist文件夹,当build:client命令执行完后,再移入dist中;build:mac命令为在mac下的构建命令,作用与build命令相同;dev命令就是将serve:serverserve:client命令结合,开启本地服务(既可以看到服务端渲染的页面也可以看到客户端渲染的页面)。

那么入口文件skeleton-entry.js中是什么呢?类似于main.js文件,创建一个vue实例,并将骨架屏组件引入,如下:

// skeleton-entry.js
import Vue from 'vue'
import HelloWorldSkeleton from './HelloWorldSkeleton.vue'

export default new Vue({
  components: {
    HelloWorldSkeleton
  },
  template: '<hello-world-skeleton />'
})

skeleton.js用于将服务端打包生成的json文件通过vue-server-renderer写入到index.html中,具体如下:

// skeleton.js
const fs = require('fs')
const { resolve } = require('path')
const bundle = require('./dist/vue-ssr-server-bundle.json')

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

// 读取`skeleton.json`,以`index.html`为模板写入内容
const renderer = createBundleRenderer(bundle, {
  template: fs.readFileSync(resolve(__dirname, './public/index.html'), 'utf-8')
})

// 把上一步模板完成的内容写入(替换)`index.html`
renderer.renderToString({}, (err, html) => {
  console.log(html)
  fs.writeFileSync('./public/index.html', html, 'utf-8')
})

至此,在vue项目中引用骨架屏的demo就完成了,这只是一个简单的demo,实际项目中还需要考虑路由切换时骨架屏加载问题,以及开发过程中服务端渲染的热更新实现问题,后面将会继续深入。

demo源码可在github上获取

参考文献:

[1] Vue页面骨架屏
[2] Vue 页面骨架屏注入实践
[3] 通过vue-cli3构建一个SSR应用程序

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值