vue怎样实现骨架屏?思路和方法是什么?

Admin 2021-10-23 群英技术资讯 766 次浏览

    vue怎样实现骨架屏?骨架屏的应用可以作为loading 使用,小编觉得还是比较实用的,因此这篇就给大家来分享一下用vue实现骨架屏的思路及方法,感兴趣的朋友可以了解看看。

    骨架屏用途

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

    Vue架构骨架屏

    思路大纲

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

    定义一个抽象组件

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

        export default {
          name: 'GmSkeleton',
          abstract: true // 抽象组件的属性
        }
    

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

        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操作类名

        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的静态类名

        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类名

    使用方法

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

    传值

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

    效果如下

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

        以上就是关于vue实现骨架屏的思路及代码啦,本文代码有一定的借鉴参考价值,希望大家阅读完这篇文章能有所收获。最后,想要了解更多可以继续浏览群英网络其他相关的文章。
    文本转载自脚本之家

    群英智防CDN,智能加速解决方案
    标签: vue实现骨架屏

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

    猜你喜欢

    成为群英会员,开启智能安全云计算之旅

    立即注册
    专业资深工程师驻守
    7X24小时快速响应
    一站式无忧技术支持
    免费备案服务
    免费拨打  400-678-4567
    免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
    在线客服
    微信公众号
    返回顶部
    返回顶部 返回顶部
    在线客服
    在线客服