Vue页面骨架屏的实现方法


Posted in Javascript onMay 22, 2018

在开发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

文章相关代码已经同步到Github,欢迎查阅~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
元素的内联事件处理函数的特殊作用域在各浏览器中存在差异
Jan 12 Javascript
javascript分页代码实例分享(js分页)
Dec 13 Javascript
node.js中的emitter.on方法使用说明
Dec 10 Javascript
jQuery实现页面滚动时动态加载内容的方法
Mar 20 Javascript
jQuery中serializeArray()与serialize()的区别实例分析
Dec 09 Javascript
jQuery实现底部浮动窗口效果
Sep 07 Javascript
微信小程序 LOL 英雄介绍开发实例
Sep 30 Javascript
详解js数组的完全随机排列算法
Dec 16 Javascript
js实现添加删除表格(两种方法)
Apr 27 Javascript
在HTML文档中嵌入JavaScript的四种方法
May 07 Javascript
Node.js net模块功能及事件监听用法分析
Jan 05 Javascript
vuex(vue状态管理)的特殊应用案例分享
Mar 03 Javascript
Angular网络请求的封装方法
May 22 #Javascript
vue input输入框模糊查询的示例代码
May 22 #Javascript
vue 中swiper的使用教程
May 22 #Javascript
vue配置多页面的实现方法
May 22 #Javascript
vue.js使用v-model指令实现的数据双向绑定功能示例
May 22 #Javascript
详解js类型判断
May 22 #Javascript
vue.js过滤器+ajax实现事件监听及后台php数据交互实例
May 22 #Javascript
You might like
php curl模拟post请求小实例
2013/11/13 PHP
PHP MPDF中文乱码的解决方式
2015/12/08 PHP
php 下 html5 XHR2 + FormData + File API 上传文件操作实例分析
2020/02/28 PHP
js的闭包的一个示例说明
2008/11/18 Javascript
基于JQuery的多标签实现代码
2012/09/19 Javascript
Jquery 自定义动画概述及示例
2013/03/29 Javascript
js 距离某一时间点时间是多少实现代码
2013/10/14 Javascript
js中一维数组和二位数组中的几个问题示例说明
2014/07/17 Javascript
jquery配合.NET实现点击指定绑定数据并且能够一键下载
2016/10/28 Javascript
smartupload实现文件上传时获取表单数据(推荐)
2016/12/12 Javascript
xmlplus组件设计系列之分隔框(DividedBox)(8)
2017/05/02 Javascript
JavaScript实现简单的星星评分效果
2017/05/18 Javascript
JQuery EasyUI的一些常用组件
2017/07/12 jQuery
JavaScript 数组的进化与性能分析
2017/09/18 Javascript
mui框架 页面无法滚动的解决方法(推荐)
2018/01/25 Javascript
vue微信分享到朋友圈 vue微信发送给好友
2018/11/28 Javascript
Jquery获取radio选中值实例总结
2019/01/17 jQuery
Node4-5静态资源服务器实战以及优化压缩文件实例内容
2019/08/29 Javascript
Python 时间操作例子和时间格式化参数小结
2014/04/24 Python
python使用nntp读取新闻组内容的方法
2015/05/08 Python
详解python实现线程安全的单例模式
2018/03/05 Python
Python cookbook(数据结构与算法)通过公共键对字典列表排序算法示例
2018/03/15 Python
浅谈pyqt5中信号与槽的认识
2019/02/17 Python
python简单鼠标自动点击某区域的实例
2019/06/25 Python
处理python中多线程与多进程中的数据共享问题
2019/07/28 Python
PyTorch在Windows环境搭建的方法步骤
2020/05/12 Python
jupyter notebook更换皮肤主题的实现
2021/01/07 Python
25个CSS3动画按钮和菜单教程分享
2012/10/03 HTML / CSS
一款恶搞头像特效的制作过程 利用css3和jquery
2014/11/21 HTML / CSS
canvas绘制视频封面的方法
2018/02/05 HTML / CSS
美国专业消费电子及摄影器材网站:B&H Photo Video
2019/12/18 全球购物
校长岗位职责
2013/11/26 职场文书
文秘人员工作职责
2014/01/31 职场文书
优秀语文教师事迹
2014/05/18 职场文书
年会主持人开场白台词
2015/05/29 职场文书
2019邀请函格式及范文
2019/05/20 职场文书