Preparation before starting

  1. Clone elementui’s Master branch completely, and portal

  2. NPM run dev:play NPM run dev:play NPM run dev:play NPM run dev:play On success, open localhost:8085,

  3. Because NPM run dev:play is executed, the code is as follows

"dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js"
Copy the code

We then look at build/webpack.demo.js and find the following code

entry: isProd ? {
    docs: './examples/entry.js'
  } : (isPlay ? './examples/play.js' : './examples/entry.js'),
Copy the code

Obviously, our entry point is./examples/play.js, and then we follow this file to the examples/play folder in the vue file, and we are done.

The official start of the

Document analysis

This component can be called either by instruction (via v-loading) or by Vue instance method (via this.$loading).

The directory structure

The code of the index file in the loading folder is as follows

import directive from './src/directive';
import service from './src/index';

export default {
  install(Vue) {
    Vue.use(directive);
    Vue.prototype.$loading = service;
  },
  directive,
  service
};
Copy the code

/ SRC /directive supports adding Vue instance method calls./ SRC /index supports adding Vue instance method calls. In addition, the install method is added on the object to facilitate the user to selectively obtain components through vue. use

Add Vue instance method

  1. Viewing source files

    From the directory structure, we already know that the source files for both method calls are actuallyloading.vueFile, the other two are just extensions on this basis

    The source code is as follows:
<template>
  <transition name="el-loading-fade" @after-leave="handleAfterLeave">
    <div
      v-show="visible"
      class="el-loading-mask"
      :style="{ backgroundColor: background || '' }"
      :class="[customClass, { 'is-fullscreen': fullscreen }]">
      <div class="el-loading-spinner">
        <svg v-if=! "" spinner" class="circular" viewBox="25 25 to 50 50">
          <circle class="path" cx="50" cy="50" r="20" fill="none"/>
        </svg>
        <i v-else :class="spinner"></i>
        <p v-if="text" class="el-loading-text">{{ text }}</p>
      </div>
    </div>
  </transition>
</template>

<script>
  export default {
    data() {
      return {
        text: null.spinner: null.background: null.fullscreen: true.visible: false.customClass: ' '
      };
    },

    methods: {
      handleAfterLeave() {
        this.$emit('after-leave');
      },
      setText(text) {
        this.text = text; }}};</script>
Copy the code

The transition animation package is an absolute positioning box that contains the default SVG and self-positioning text, followed by an extension to index.js in the SRC folder. Because a large portion of the code deals with styles and hierarchies, I’ve left it out and just show the core code below:

import loadingVue from './loading.vue';

const LoadingConstructor = Vue.extend(loadingVue);
        const defaults = {
            text: null.fullscreen: true.body: false.lock: false.customClass: ' '
        };
        let fullscreenLoading;// To save the popover instance
        // Close the popover method
        function afterLeave(instance, callback, speed = 300, once = false) {
            let called = false;
            const afterLeaveCallback = function () {
                if (called) return;
                called = true;
                if (callback) {
                    callback.apply(null.arguments); }};setTimeout(() = > {
                afterLeaveCallback();
            }, speed + 100);
        };

        LoadingConstructor.prototype.close = function () {
            // Clear the popover instance
            if (this.fullscreen) {
                fullscreenLoading = undefined;
            }
            afterLeave(this._= > {
                // Delete the node
                if (this.$el && this.$el.parentNode) {
                    this.$el.parentNode.removeChild(this.$el);
                }
                this.$destroy();
            }, 300);
            this.visible = false;
        };
        let service = (options = {}) = > {
            // Add Object. Assign profill
            options = Object.assign({}, defaults, options);
            if (typeof options.target === 'string') {
                options.target = document.querySelector(options.target);
            }
            options.target = options.target || document.body;
            if(options.target ! = =document.body) {
                options.fullscreen = false;
            } else {
                options.body = true;
            }
            // There can only be one loading to cover the entire body
            if (options.fullscreen && fullscreenLoading) {
                return fullscreenLoading;
            }
            let instance = new LoadingConstructor({
                el: document.createElement('div'),
                data: options
            });
            // If there is no target, the popover will be mounted directly on the body
            let parent = options.body ? document.body : options.target;
            parent.appendChild(instance.$el);
            Vue.nextTick(() = > {
                instance.visible = true;
            });
            // Assign the instance to fullscreenLoading
            if (options.fullscreen) {
                fullscreenLoading = instance;
            }
            return instance;
        }

        Vue.prototype.$loading = service
Copy the code

So let’s verify this one last time

<div id="app">
        <! As the loading component is absolutely positioned, the parent node will be added el-loading-parent--relative-->
        <div id="loading" class="el-loading-parent--relative"></div>
    </div>
Copy the code

instantiation

new Vue({ el: '#app', data: function () { return { visible: true } }, mounted() { const loading = this.$loading({ lock: True, text: 'Loading', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)', target: "#loading" }) setTimeout(() => { loading.close(); }, 2000); }})Copy the code

The renderings are as follows

Custom instruction

For custom directives, the detailed pre-information is still the most complete official documentation, and the next step in the portal is the source code in Elementui, which I have simplified to make it easier to read

import loadingVue from './loading.vue';
const Mask = Vue.extend(loadingVue);
Vue.directive('loading', {
    // only called once when the directive is first bound to an element. This is where you can perform one-time initialization Settings
    bind: function (el, binding, vnode) {
        const textExr = el.getAttribute('element-loading-text');
        const spinnerExr = el.getAttribute('element-loading-spinner');
        const backgroundExr = el.getAttribute('element-loading-background');
        const customClassExr = el.getAttribute('element-loading-custom-class');
        const vm = vnode.context;
        const mask = new Mask({
            el: document.createElement('div'),
            data: {
                text: vm && vm[textExr] || textExr,
                spinner: vm && vm[spinnerExr] || spinnerExr,
                background: vm && vm[backgroundExr] || backgroundExr,
                customClass: vm && vm[customClassExr] || customClassExr,
                fullscreen:!!!!! binding.modifiers.fullscreen } }); el.instance = mask; el.mask = mask.$el; el.maskStyle = {}; binding.value && toggleLoading(el, binding); },update: function (el, binding) {
        el.instance.setText(el.getAttribute('element-loading-text'));
        if (binding.oldValue !== binding.value) {
            toggleLoading(el, binding);
        }
    },

    unbind: function (el, binding) {
        if (el.domInserted) {
            el.mask &&
                el.mask.parentNode &&
                el.mask.parentNode.removeChild(el.mask);
            toggleLoading(el, { value: false.modifiers: binding.modifiers }); } el.instance && el.instance.$destroy(); }});const toggleLoading = (el, binding) = > {
    if (binding.value) {
        Vue.nextTick(() = > {
            // Determine the fullscreen, if any, and bind it to the body
            if (binding.modifiers.fullscreen) {
                insertDom(document.body, el, binding);
            } else {
                // Determine whether to bind to the body
                if (binding.modifiers.body) {
                    insertDom(document.body, el, binding);
                } else{ insertDom(el, el, binding); }}}); }else {
        afterLeave(el.instance, _= > {
            if(! el.instance.hiding)return;
            el.domVisible = false;
            el.instance.hiding = false;
        }, 300.true);
        el.instance.visible = false;
        el.instance.hiding = true; }};const insertDom = (parent, el, binding) = > {
    // Determine if styles in domVisible and EL are missing or hidden
    if(! el.domVisible && el.style['display']! = ='none' && el.style['visibility']! = ='hidden') {
        Object.keys(el.maskStyle).forEach(property= > {
            el.mask.style[property] = el.maskStyle[property];
        });

        el.domVisible = true;

        parent.appendChild(el.mask);
        Vue.nextTick(() = > {
            if (el.instance.hiding) {
                el.instance.$emit('after-leave');
            } else {
                el.instance.visible = true; }}); el.domInserted =true;
    } else if (el.domVisible && el.instance.hiding === true) {
        el.instance.visible = true;
        el.instance.hiding = false; }};Copy the code

Add the install method to the loadingDirective. Add the install method to the loadingDirective. Finally, it is called in the outer index file through vue.use, and the simplified code is as follows

const loadingDirective = {};
loadingDirective.install = Vue= > {
// The above code
}
Copy the code

Below is a sketch of the source codeAt this point, the loading component’s source code analysis is complete