Example of implementing skeleton screen with Vue

Example of implementing skeleton screen with Vue

Skeleton screen use

  • As the loading of route switching in spa, it is used in combination with the life cycle of the component and the timing of the return of the ajax request. (Used as loading). As a front-end developer who has the closest contact with users, user experience is the issue that deserves the most attention. Regarding the display of page loading status, there are two mainstream ones: loading picture and progress bar. In addition, more and more APPs use the "skeleton screen" method to display unloaded content, giving users a brand new experience.
  • As an optimization for first screen rendering

Vue architecture skeleton screen

Outline of ideas

  • Define an abstract component and get the slot in the render function of the abstract component
  • Deep loop through the slots and add the class name of gm-skeleton to each element
  • Pre-pause and clear vnode textContent to ensure that the default text does not appear when the skeleton screen appears
  • Return slots

Defining an abstract component

What is an abstract component? A component that is skipped during rendering and only performs runtime operations.

    export default {
      name: 'GmSkeleton',
      abstract: true // properties of abstract components}

Get the slot and initialize the operation skeleton screen

    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
    }

Here we put the slot processing method in nextTick, because the handlerPrefix needs to get the real DOM. NextTick is used to execute all methods in the sorted update queue. Before executing render, the renderWatcher of the GMSkeleton component has been collected in the update queue. Therefore, the nextTick CallBack function can get all the real DOM in the corresponding slots after rendering. If you don't understand the principle of nextTick, please move to what you don't know about nextTick.

Cycle slots operation class name

    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 = () => { // Function refactoring, modify the original component hook, and ensure that insert is only executed once 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)
      })
    },

Step by step analysis:

  1. We iterate through the slots
  2. Get the children collection under the current vnode for the next loop
  3. Determine whether it is a native HTML element. Only component vnode will have the componentOptions attribute
  4. Determine whether it is an abstract component. We know that abstract components will not be rendered to the real DOMTree, such as keep-alive and transition. Each component's vnode has a unique hooks lifecycle: init, insert, prepatch, and destroy. Each lifecycle will be triggered at different stages. Hijack insert, retain the original insert method, and then reconstruct the vnode's insert method to call the handlerComponent method to add the class name. This is similar to the usage concept of mounted nextTick above. Since handlerComponent needs to know the instance of the child component, it must be called after instantiation. The component's init method will instantiate the component and directly call watcher.update(watcher.render()), that is, when we call the insert method, it is actually after update(render()), so the instantiated child component can be obtained here.
  5. Determine whether nodeType is a text node. If so, save textContent and then delete it to ensure that the default text will not be displayed when the skeleton screen appears. When the skeleton screen disappears, return the previously retained default text to vnode, so that you can freely switch between displaying and hiding the skeleton screen.

The static class name for operating 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 is ​​used to add the gm-skeleton class name, while removeSkeletonPrefix is ​​used to delete the gm-skeleton class name

How to use

  import Vue from 'vue'
  import GMSkeleton from 'path/to/GMSkeleton'
  
  Vue.use(GMSkeleton)
  <gm-skeleton>
    <Component />
    <div></div>
    <div><span>Front-end Martin</span></div>
  </gm-skeleton>

Passing Values

Property name value describe
showSpin Boolean Whether to open the skeleton screen, the default is true
skeletPrefix String Skeleton screen class name, the default is gm-skeleton

The effect is as follows

The specific style is generated according to the style written by the developer, wrapped by gm-skeleton, as shown above. The following is a simple example

Full address

80 lines of code to implement Vue skeleton screen

The above is the details of the example of implementing the skeleton screen with vue. For more information about implementing the skeleton screen with vue, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • A brief discussion on the practice of Vue project skeleton screen injection
  • Skeleton screen practice based on vue-skeleton-webpack-plugin
  • Configuration method of injecting skeleton screen into vue mobile terminal
  • Detailed explanation of VUE single-page application skeleton screen solution
  • Example of how to build a skeleton screen using vue-cli
  • How to implement the skeleton screen of Vue page
  • Vue page skeleton screen injection method
  • About Vue single page skeleton screen practice record

<<:  Detailed steps for installing the decompressed version of MySQL 5.7.20 (two methods)

>>:  Build a Scala environment under Linux and write a simple Scala program

Recommend

JS Object constructor Object.freeze

Table of contents Overview Example 1) Freeze Obje...

Negative margin function introduction and usage summary

As early as in the CSS2 recommendations in 1998, t...

Get / delete method to pass array parameters in Vue

When the front-end and back-end interact, sometim...

Can MySQL's repeatable read level solve phantom reads?

introduction When I was learning more about datab...

MySQL optimization query_cache_limit parameter description

query_cache_limit query_cache_limit specifies the...

Write a publish-subscribe model with JS

Table of contents 1. Scene introduction 2 Code Op...

React Hooks Common Use Scenarios (Summary)

Table of contents 1. State Hook 1. Basic usage 2....

Detailed explanation of Redis master-slave replication practice using Docker

Table of contents 1. Background 2. Operation step...

Why should the number of rows in a single MySQL table not exceed 5 million?

Today, let’s discuss an interesting topic: How mu...

How to change the encoding of MySQL database to utf8mb4

The utf8mb4 encoding is a superset of the utf8 en...

Implementing a simple whack-a-mole game in JavaScript

This article shares the specific code for JavaScr...

MySQL query redundant indexes and unused index operations

MySQL 5.7 and above versions provide direct query...

Node uses async_hooks module for request tracking

The async_hooks module is an experimental API off...