服务器之家:专注于服务器技术及软件下载分享
分类导航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服务器之家 - 编程语言 - JavaScript - vue.js - vue实现骨架屏的示例

vue实现骨架屏的示例

2022-03-09 16:26我在曾经眺望彼岸 vue.js

这篇文章主要介绍了vue实现骨架屏的示例,帮助大家更好的理解和学习使用vue框架,感兴趣的朋友可以了解下

骨架屏用途

  • 作为spa中路由切换的 loading, 结合组件的生命周期和ajax请求返回的时机来使用.( 作为loading 使用)。作为与用户联系最为密切的前端开发者,用户体验是最值得关注的问题。关于页面loading状态的展示,主流的主要有loading图和进度条两种。除此之外,越来越多的APP采用了“骨架屏”的方式去展示未加载内容,给予了用户焕然一新的体验。
  • 作为首屏渲染的优化

Vue架构骨架屏

思路大纲

  • 定义一个抽象组件,在抽象组件的render函数里获取插槽
  • 深度循环遍历插槽,将每个元素都添加上gm-skeleton的类名
  • 将vnode textContent预暂后清空保证骨架屏出现时不会出现默认文字
  • 返回slots

定义一个抽象组件

什么是抽象组件? 在渲染时会被跳过,只做运行时的操作的组件

?
1
2
3
4
export default {
  name: 'GmSkeleton',
  abstract: true // 抽象组件的属性
}

获取插槽并初始化操作骨架屏

?
1
2
3
4
5
6
7
8
9
10
render(h) {
  const slots = this.$slots.default || [h('')]
  this.$nextTick().then(() => {
    this.handlerPrefix(slots, this.showSpin ? this.addSkeletPrefix : this.removeSkeletPrefix)
  })
 
  return slots.length > 1 ? h('div', {
     staticClass: this.showSpin ? 'g-spinner' : ''
  }, slots) : slots
}

这里我们将处理slots的方法放置在nextTick里面, 因为handlerPrefix里需要获取真实的DOM,nextTick是用来执行排序后的更新队列里的所有方法, 在执行render前, GMSkeleton组件的renderWatcher已被收集到更新队列里,所以此时定义nextTick CallBack函数里能获取到渲染后对应插槽里所有真实DOM,若是不了解nextTick原理,请移步你不知道的nextTick

循环slots操作类名

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
handlerComponent(slot, handler/* addSkeletPrefix | removeSkeletPrefix */, init) {
  const originchildren = (((slot.componentInstance || {})._vnode || {}).componentOptions || {}).children
  const compchildren = ((slot.componentInstance || {})._vnode || {}).children
  !init && handler(slot)
  if (compchildren) this.handlerPrefix(compchildren, handler, false)
  if (originchildren) this.handlerPrefix(originchildren, handler, false)
},
handlerPrefix(slots, handler, init = true) {
  slots.forEach(slot => {
    var children = slot.children || (slot.componentOptions || {}).children || ((slot.componentInstance || {})._vnode || {}).children
    if (slot.data) {
      if (!slot.componentOptions) {
        !init && handler(slot)
      } else if (!this.$hoc_utils.getAbstractComponent(slot)) {
        ;(function(slot) {
          const handlerComponent = this.handlerComponent.bind(this, slot, handler, init)
          const insert = (slot.data.hook || {}).insert
          ;(slot.data.hook || {}).insert = () => { // 函数重构, 修改原有的组件hook, 并且保证insert只执行一次
            insert(slot)
            handlerComponent()
          }
          ;(slot.data.hook || {}).postpatch = handlerComponent
        }).call(this, slot)
      }
    }
    if (slot && slot.elm && slot.elm.nodeType === 3) {
      if (this.showSpin) {
        slot.memorizedtextContent = slot.elm.textContent
        slot.elm.textContent = ''
      } else {
        slot.elm.textContent = slot.memorizedtextContent || slot.elm.textContent || slot.text
      }
    }
    children && this.handlerPrefix(children, handler, false)
  })
},

逐步分析:

  1. 我们遍历slots插槽
  2. 获取当前vnode下的children集合以备做下一次循环
  3. 判断是否是原生HTML元素,只有组件vnode才会具备componentOptions属性
  4. 判断是否抽象组件,我们知道抽象组件是不会渲染到真实DOMTree上的,例如keep-alive、transition,每个组件的vnode拥有独有的hooks生命周期: init(初始化)、insert(插入)、prepatch(更新)、destroy(销毁),每个生命周期会在不同阶段触发, 劫持insert,保留原有的insert方法,随后重构vnode的insert方法在里面调用handlerComponent方法进行添加类名,这里与上面的mounted的nextTick用法理念类似,由于handlerComponent需要知道子组件的实例,所以必须在实例化后去调用,而组件的init方法会实例组件并且直接调用watcher.update(watcher.render()), 也就是我们在调用insert方法的时候其实是在update(render())后,所以这里能够获取到实例化后子组件
  5. 判断nodeType是否是文本节点,若是的话需要先将textContent保存后进行删除,保证在骨架屏出现时不会显示默认文字,在骨架屏消失时,将原先保留的默认文字返回给vnode,这样就能自由在骨架屏的显示隐藏期间自由切换

操作vnode的静态类名

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
addSkeletPrefix(slot) {
  const rootVnode = slot.componentOptions ? (slot.componentInstance || {})._vnode || {} : slot;
  if (rootVnode.elm) {
    rootVnode.elm.classList.add(this.skeletPrefix)
  } else {
    ;(rootVnode.data || {}).staticClass += ` ${this.skeletPrefix}`
  }
},
removeSkeletPrefix(slot) {
  const rootVnode = slot.componentOptions ? (slot.componentInstance || {})._vnode || {} : slot;
  if (rootVnode.elm) {
    rootVnode.elm.classList && rootVnode.elm.classList.remove(this.skeletPrefix)
  } else if (rootVnode.data.staticClass) {
    rootVnode.data.staticClass = rootVnode.data.staticClass.replace(` ${this.skeletPrefix}`, '')
  }
}

addSkeletePrefix用于添加gm-skeleton类名,而removeSkeletonPrefix则是用于删除gm-skeleton类名

使用方法

?
1
2
3
4
import Vue from 'vue'
import GMSkeleton from 'path/to/GMSkeleton'
 
Vue.use(GMSkeleton)
?
1
2
3
4
5
<gm-skeleton>
  <Component />
  <div></div>
  <div><span>前端马丁</span></div>
</gm-skeleton>

传值

 

属性名 描述
showSpin Boolean 是否开启骨架屏,默认为true
skeletPrefix String 骨架屏类名, 默认是gm-skeleton

 

效果如下

具体样式是根据开发者自己写的样式来生成的,通过gm-skeleton包裹,如上的使用方法,以下是一个简单的例子

vue实现骨架屏的示例

完整地址

80行代码实现Vue骨架屏

以上就是vue实现骨架屏的示例的详细内容,更多关于vue实现骨架屏的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/6954245046690381831

延伸 · 阅读

精彩推荐
  • vue.jsVue2.x 项目性能优化之代码优化的实现

    Vue2.x 项目性能优化之代码优化的实现

    这篇文章主要介绍了Vue2.x 项目性能优化之代码优化的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋...

    优小U9632022-02-21
  • vue.js用vite搭建vue3应用的实现方法

    用vite搭建vue3应用的实现方法

    这篇文章主要介绍了用vite搭建vue3应用的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下...

    Asiter7912022-01-22
  • vue.jsVue多选列表组件深入详解

    Vue多选列表组件深入详解

    这篇文章主要介绍了Vue多选列表组件深入详解,这个是vue的基本组件,有需要的同学可以研究下...

    yukiwu6752022-01-25
  • vue.js梳理一下vue中的生命周期

    梳理一下vue中的生命周期

    看过很多人讲vue的生命周期,但总是被绕的云里雾里,尤其是自学的同学,可能js的基础也不是太牢固,听起来更是吃力,那我就已个人之浅见,以大白话...

    CRMEB技术团队7992021-12-22
  • vue.js详解vue 表单绑定与组件

    详解vue 表单绑定与组件

    这篇文章主要介绍了vue 表单绑定与组件的相关资料,帮助大家更好的理解和学习使用vue框架,感兴趣的朋友可以了解下...

    Latteitcjz6432022-02-12
  • vue.jsVue中引入svg图标的两种方式

    Vue中引入svg图标的两种方式

    这篇文章主要给大家介绍了关于Vue中引入svg图标的两种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    十里不故梦10222021-12-31
  • vue.jsVue2.x-使用防抖以及节流的示例

    Vue2.x-使用防抖以及节流的示例

    这篇文章主要介绍了Vue2.x-使用防抖以及节流的示例,帮助大家更好的理解和学习使用vue框架,感兴趣的朋友可以了解下...

    Kyara6372022-01-25
  • vue.jsVue项目中实现带参跳转功能

    Vue项目中实现带参跳转功能

    最近做了一个手机端系统,其中遇到了父页面需要携带参数跳转至子页面的问题,现已解决,下面分享一下实现过程,感兴趣的朋友一起看看吧...

    YiluRen丶4302022-03-03