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

一、分析Vue页面的内容加载过程

vue项目中的入口index.html只有简单的内容:

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  5. <title>Document</title>
  6. </head>
  7. <body>
  8. <div id="root">
  9. </div>
  10. <script type="text/javascript" src="bundle.js"></script></body>
  11. </body>
  12. </html>

当js执行完之后,会用vue渲染成的dom将div#root完全替换掉。
我们在div#root中加入模拟骨架屏,在Chrome开发者工具调整网速:

  1. <div id="root">
  2. 这里是骨架屏
  3. </div>

由此可知,将骨架屏内容直接插入div#root中即可实现骨架屏。

二、使用vue-server-renderer来实现骨架屏

我们需要骨架屏也是一个单独的.vue文件,因此我们需要用到vue-server-renderer。对vue服务端渲染有所了解的同学一定知道,这个插件能够将vue项目在node端打包成一个bundle,然后由bundle生成对应的html。
首先是生成项目:

  1. .
  2. ├── build
  3. ├── webpack.config.client.js
  4. └── webpack.config.server.js
  5. ├── src
  6. └── views
  7. ├── index
  8. └── index.vue
  9. ├── skeleton
  10. └── skeleton.vue
  11. ├── app.vue
  12. ├── index.js
  13. └── skeleton-entry.js
  14. ├── index.html
  15. └── skeleton.js
  16. └── package.json

vue的服务端渲染一般会用vue-server-renderer将整个项目在node端打包成一份bundle,而这里我们只要一份有骨架屏的html,所以会有一个单独的骨架屏入口文件skeleton-entry.js,一个骨架屏打包webpack配置webpack.config.server.js,而skeleton.js作用是将webpack打包出来的bundle写入到index.html中。

  1. //skeleton-entry.js
  2. import Vue from 'vue'
  3. import Skeleton from './views/skeleton/skeleton.vue'
  4.  
  5. export default new Vue({
  6. components: {
  7. Skeleton
  8. },
  9. template: '<skeleton />'
  10. })
  1. //webpack.config.server.js
  2. const path = require('path')
  3. const { VueLoaderPlugin } = require('vue-loader')
  4. const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
  5.  
  6. module.exports = {
  7. mode: process.env.NODE_ENV,
  8. target: 'node',
  9. entry: path.join(__dirname, '../src/skeleton-entry.js'),
  10. output: {
  11. path: path.join(__dirname, '../server-dist'),
  12. filename: 'server.bundle.js',
  13. libraryTarget: 'commonjs2'
  14. },
  15. module: {
  16. rules: [
  17. {
  18. test: /\.vue$/,
  19. loader: 'vue-loader'
  20. },
  21. {
  22. test: /\.css$/,
  23. use: [
  24. 'vue-style-loader',
  25. 'css-loader'
  26. ]
  27. }
  28. ]
  29. },
  30. externals: Object.keys(require('../package.json').dependencies),
  31. resolve: {
  32. alias: {
  33. 'vue$': 'vue/dist/vue.esm.js'
  34. }
  35. },
  36. plugins: [
  37. new VueLoaderPlugin(),
  38. new VueSSRServerPlugin({
  39. filename: 'skeleton.json'
  40. })
  41. ]
  42. }

其中骨架屏的webpack配置因为是node端,所以需要target: 'node' libraryTarget: 'commonjs2'。在VueSSRServerPlugin中,指定了其输出的json文件名。当执行webpack会在/server-dist目录下生成一个skeleton.json文件,这个文件记载了骨架屏的内容和样式,会提供给vue-server-renderer使用。

  1. //skeleton.js
  2. const fs = require('fs')
  3. const path = require('path')
  4.  
  5. const createBundleRenderer = require('vue-server-renderer').createBundleRenderer
  6.  
  7. // 读取`skeleton.json`,以`index.html`为模板写入内容
  8. const renderer = createBundleRenderer(path.join(__dirname, './server-dist/skeleton.json'), {
  9. template: fs.readFileSync(path.join(__dirname, './index.html'), 'utf-8')
  10. })
  11.  
  12. // 把上一步模板完成的内容写入(替换)`index.html`
  13. renderer.renderToString({}, (err, html) => {
  14. fs.writeFileSync('index.html', html, 'utf-8')
  15. })
  1. 注意,作为模板的html文件,需要在被写入内容的位置添加<!--vue-ssr-outlet-->占位符,本例子在div#root里写入:
  2.  
  3. <div id="root">
  4. <!--vue-ssr-outlet-->
  5. </div>

最后执行node skeleton就能实现vue的骨架屏。
最终的index.html:

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  7. <title>Document</title>
  8. <style data-vue-ssr-id="a7049cb4:0">
  9. .skeleton[data-v-61761ff8] {
  10. position: relative;
  11. height: %;
  12. overflow: hidden;
  13. padding: 15px;
  14. box-sizing: border-box;
  15. background: #fff;
  16. }
  17. .skeleton-nav[data-v-61761ff8] {
  18. height: 45px;
  19. background: #eee;
  20. margin-bottom: 15px;
  21. }
  22. .skeleton-swiper[data-v-61761ff8] {
  23. height: 160px;
  24. background: #eee;
  25. margin-bottom: 15px;
  26. }
  27. .skeleton-tabs[data-v-61761ff8] {
  28. list-style: none;
  29. padding: ;
  30. margin: -15px;
  31. display: flex;
  32. flex-wrap: wrap;
  33. }
  34. .skeleton-tabs-item[data-v-61761ff8] {
  35. width: %;
  36. height: 55px;
  37. box-sizing: border-box;
  38. text-align: center;
  39. margin-bottom: 15px;
  40. }
  41. .skeleton-tabs-item span[data-v-61761ff8] {
  42. display: inline-block;
  43. width: 55px;
  44. height: 55px;
  45. border-radius: 55px;
  46. background: #eee;
  47. }
  48. .skeleton-banner[data-v-61761ff8] {
  49. height: 60px;
  50. background: #eee;
  51. margin-bottom: 15px;
  52. }
  53. .skeleton-productions[data-v-61761ff8] {
  54. height: 20px;
  55. margin-bottom: 15px;
  56. background: #eee;
  57. }
  58. </style></head>
  59. <body>
  60. <div id="root">
  61. <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>
  62. </div>
  63. </body>
  64. </html>

尾声

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

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

Vue页面骨架屏(一)的更多相关文章

  1. Vue页面骨架屏(二)

    实现思路 参考原文中在构建时使用 Vue 预渲染骨架屏一节介绍的思路,我将骨架屏也看成路由组件,在构建时使用 Vue 预渲染功能,将骨架屏组件的渲染结果 HTML 片段插入 HTML 页面模版的挂载点 ...

  2. Vue 项目骨架屏注入与实践

    作为与用户联系最为密切的前端开发者,用户体验是最值得关注的问题.关于页面loading状态的展示,主流的主要有loading图和进度条两种.除此之外,越来越多的APP采用了“骨架屏”的方式去展示未加载 ...

  3. vue搭建骨架屏步骤配置

    1.什么是骨架屏幕? 在页面加载数据之前,有一段空白时间,要么用loading加载,要么就用骨架屏. 在开发webapp的时候总是会受到首屏加载时间过长的影响,主流的解决方法是在载入完成之前显示loa ...

  4. Vue单页面骨架屏实践

    github 地址: VV-UI/VV-UI 演示地址: vv-ui 文档地址:skeleton 关于骨架屏介绍 骨架屏的作用主要是在网络请求较慢时,提供基础占位,当数据加载完成,恢复数据展示.这样给 ...

  5. Vue项目骨架屏注入实践

    相比于早些年前后端代码紧密耦合.后端工程师还得写前端代码的时代,如今已发展到前后端分离,这种开发方式大大提升了前后端项目的可维护性与开发效率,让前后端工程师关注于自己的主业.然而在带来便利的同时,也带 ...

  6. 骨架屏(page-skeleton-webpack-plugin)初探

    作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/2436173500265335 微信公众 ...

  7. 【vue】饿了么项目-页面骨架开发

    1.页面骨架开发 1.1组件拆分 手机浏览器是把页面放在一个虚拟的“窗口”(viewport)中,通常这个虚拟的“窗口”(viewport)比屏幕宽,这样就不用把每个网页挤到很小的窗口中(这样会破坏没 ...

  8. vue骨架屏以及seo优化

    参考文档 vue骨架屏 https://blog.csdn.net/ly124100427/article/details/81168908 vue seo优化 1.SSR服务器渲染: 2.静态化: ...

  9. 《前端之路》之 前端图片 类型 & 优化 & 预加载 & 懒加载 & 骨架屏

    目录 09: 前端图片 类型 & 优化 & 预加载 & 懒加载 & 骨架屏 09: 前端图片 类型 & 优化 & 预加载 & 懒加载 & ...

随机推荐

  1. 进击的Python【第八章】:动态导入模块、断言、socket开发之SSH,FTP

    一.动态导入模块 知道一个模块名的字符串形式,通过字符串来导入模块 mod = __import__("lib.aa") print(mod) instance = getattr ...

  2. Mondriaan's Dream POJ - 2411

    Mondriaan's Dream POJ - 2411 可以用状压dp,但是要打一下表.暴力枚举行.这一行的状态.上一行的状态,判断如果上一行的状态能转移到这一行的状态就转移. 状态定义:ans[i ...

  3. JEECMS9.3集成dubbo操作记录

    需求描述: 门户及其他应用系统需要查询JEECMS9.3中发布的栏目及数据,而其他系统都是基于dubbo开发的,因此想要将JEECMS9.3中集成dubbo并对外提供内容管理服务. 需求实现: 1.添 ...

  4. Android偏好设置(4)设置默认值

    Setting Default Values The preferences you create probably define some important behaviors for your ...

  5. Oracle用户角色权限相关视图

    常用相关视图概述 DBA_SYS_PRIVS: 查询某个用户所拥有的系统权限 USER_SYS_PRIVS: 当前用户所拥有的系统权限 SESSION_PRIVS: 当前用户所拥有的全部权限 ROLE ...

  6. mysql之流程控制

    目录 分支结构 循环结构 分支结构: 1.if condition then [statement] elseif condition then [statement] else [statement ...

  7. javascript实现弹层效果

    首先,需要有一个按钮来模拟登录: <button id="btnLogin"class="login-btn">登录</button> ...

  8. 解决webstromm标签高亮问题

      2017/2016版  

  9. iOS Programming UISplitViewController

    iOS Programming UISplitViewController  The iPad, on the other hand, has plenty of screen space to pr ...

  10. H.264和HEVC分析软件和工具【转】

    一.264分析两大利器:264VISA和Elecard StreamEye Tools 264visa 强力的h264实时分析工具 ,能分析各种场合下的h264资源,适用于h264开发者,学习者.在图 ...