Implementing the base template

  • Path packages/loading/SRC/loading. Vue
  • This is a normal VUE page, logic is also simple, is a drive to draw mask components
<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 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

Implement service-style invocation

import Vue from 'vue'; import loadingVue from './loading.vue'; import { addClass, removeClass, getStyle } from 'element-ui/src/utils/dom'; import { PopupManager } from 'element-ui/src/utils/popup'; import afterLeave from 'element-ui/src/utils/after-leave'; import merge from 'element-ui/src/utils/merge'; // Create an object that inherits the base template const LoadingConstructor = vue.extend (loadingVue); Const defaults = {text: null, fullscreen: true, body: const defaults = {text: null, fullscreen: true, body: const defaults = {text: null, fullscreen: true, body: false, lock: false, customClass: '' }; let fullscreenLoading; / / initialize position and overflow attributes is empty LoadingConstructor prototype. OriginalPosition = ' '; LoadingConstructor.prototype.originalOverflow = ''; / / here first, see below to see step 1 LoadingConstructor. Prototype. Close = function () {/ / this internal data on behalf of the components, the closing of the full screen option, FullscreenLoading if (this.fullscreen) {fullscreenLoading = undefined; } / / component removed need to remove the class from the target, and new dom node (loading) afterLeave (this, _ = > {const target = this. Fullscreen | | enclosing body? document.body : this.target; removeClass(target, 'el-loading-parent--relative'); removeClass(target, 'el-loading-parent--hidden'); if (this.$el && this.$el.parentNode) { this.$el.parentNode.removeChild(this.$el); } this.$destroy(); }, 300); this.visible = false; }; const addStyle = (options, parent, instance) => { let maskStyle = {}; If (options. Fullscreen) {/ / style information for the parent instance. OriginalPosition = getStyle (document. The body, 'the position'); instance.originalOverflow = getStyle(document.body, 'overflow'); Maskstyle.zindex = popupManager.nextzIndex (); } else if (options.body) { instance.originalPosition = getStyle(document.body, 'position'); // The scrollTop and scrollLeft of the parent scrollTop and scrollLeft of the parent scrollTop ForEach (property => {let scroll === 'top'? 'scrollTop' : 'scrollLeft'; maskStyle[property] = options.target.getBoundingClientRect()[property] + document.body[scroll] + document.documentElement[scroll] + 'px'; }); // Depending on the size of the parent's viewable area, Set mask size ['height', 'width'].forEach(property => { maskStyle[property] = options.target.getBoundingClientRect()[property] + 'px'; }); } else { instance.originalPosition = getStyle(parent, 'position'); ForEach (property => {instance.$el.style[property] = maskStyle[property]; }); }; $loading = service; $loading = service; $loading = service; $loading = service; $loading = service; So full references are tied directly to our vUE. We use load.service (options) in the component; Const Loading = (options = {}) => {// If (vue.prototype. $isServer) return; Options = merge({}, defaults, options); // Loading a DOM node to overwrite. You can pass in a DOM object or string; If you pass in a string, It is passed as an argument to Document. querySelector to get the corresponding DOM node if (typeof options.target === 'string') {opts.target = document.querySelector(options.target); } options.target = options.target || document.body; If (options.target! == document.body) { options.fullscreen = false; } else { options.body = true; } // Check whether fullscreenLoading has already been initialized. If so, If (options.fullscreen && fullscreenLoading) {return fullscreenLoading; } let parent = options.body ? document.body : options.target; // Let instance = new LoadingConstructor({el: LoadingConstructor) {// Let instance = new LoadingConstructor({el: LoadingConstructor) document.createElement('div'), data: options }); addStyle(options, parent, instance); / / according to the parent orientation information to decide whether to need to increase the relative attributes the if (instance originalPosition! == 'absolute' && instance.originalPosition ! == 'fixed') { addClass(parent, 'el-loading-parent--relative'); } if (options.fullscreen && options.lock) { addClass(parent, 'el-loading-parent--hidden'); } parent.appendChild(instance.$el); Loading Vie.nexttick (() => {instance.visible = true; }); // If the loading is fullscreen, the fullscreenLoading object is assigned. Because the fullscreen is bound to the body, the parent is fixed. FullscreenLoading = instance; if (options.fullscreen) {fullscreenLoading = instance; } return instance; }; export default Loading;Copy the code

Instruction way

import Vue from 'vue';
import Loading from './loading.vue';
import { addClass, removeClass, getStyle } from 'element-ui/src/utils/dom';
import { PopupManager } from 'element-ui/src/utils/popup';
import afterLeave from 'element-ui/src/utils/after-leave';

// loading组件 继承基础组件
const Mask = Vue.extend(Loading);

const loadingDirective = {};

// install 方便被引用的时候 vue.use
loadingDirective.install = Vue => {

  // 是否已经注册过该指令了
  if (Vue.prototype.$isServer) return;

  // 暂缓查看步骤1
  const toggleLoading = (el, binding) => {
    if (binding.value) {
      // v-loading绑定是true的时候,等组件渲染完毕
      Vue.nextTick(() => {

        // 判断修饰符是不是全屏
        if (binding.modifiers.fullscreen) {
          // 获取父级样式信息
          el.originalPosition = getStyle(document.body, 'position');
          el.originalOverflow = getStyle(document.body, 'overflow');
          // 调用弹出层管理器,获取递增的zindex值
          el.maskStyle.zIndex = PopupManager.nextZIndex();

          addClass(el.mask, 'is-fullscreen');
          insertDom(document.body, el, binding);
        } else {
          removeClass(el.mask, 'is-fullscreen');

          // 判断修饰符是否挂载到body上
          // 计算位置信息,和大小信息
          if (binding.modifiers.body) {
            el.originalPosition = getStyle(document.body, 'position');

            ['top', 'left'].forEach(property => {
              const scroll = property === 'top' ? 'scrollTop' : 'scrollLeft';
              el.maskStyle[property] = el.getBoundingClientRect()[property] +
                document.body[scroll] +
                document.documentElement[scroll] -
                parseInt(getStyle(document.body, `margin-${ property }`), 10) +
                'px';
            });
            ['height', 'width'].forEach(property => {
              el.maskStyle[property] = el.getBoundingClientRect()[property] + 'px';
            });

            insertDom(document.body, el, binding);
          } else {
            el.originalPosition = getStyle(el, 'position');
            insertDom(el, el, binding);
          }
        }
      });
    } else {
      afterLeave(el.instance, _ => {
        if (!el.instance.hiding) return;
        el.domVisible = false;
        // 父组件,如果是 fullscreen,body修饰符,就是body,否则就是指令挂载本身
        // 移除样式, el.instance.hiding设置为false,防止多次卸载
        const target = binding.modifiers.fullscreen || binding.modifiers.body
          ? document.body
          : el;
        removeClass(target, 'el-loading-parent--relative');
        removeClass(target, 'el-loading-parent--hidden');

        // 设置hiding为false,代表已经移除该节点
        el.instance.hiding = false;
      }, 300, true);
      el.instance.visible = false;
      el.instance.hiding = true;
    }
  };
  const insertDom = (parent, el, binding) => {
    // 指令绑定的本身的 domVisible 要是false,防止重复插入, 同时本身不能是隐藏的
    if (!el.domVisible && getStyle(el, 'display') !== 'none' && getStyle(el, 'visibility') !== 'hidden') {
      // 设置蒙层的样式,位置和大小
      Object.keys(el.maskStyle).forEach(property => {
        el.mask.style[property] = el.maskStyle[property];
      });

      // 根据父级定位信息 决定父级是否需要增加relative属性
      if (el.originalPosition !== 'absolute' && el.originalPosition !== 'fixed') {
        addClass(parent, 'el-loading-parent--relative');
      }
      if (binding.modifiers.fullscreen && binding.modifiers.lock) {
        addClass(parent, 'el-loading-parent--hidden');
      }
      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) {
      // 如果 节点被隐藏了,同时hiding是true,说明已经插入了,不再重复插入,直接调用就可以了
      el.instance.visible = true;
      el.instance.hiding = false;
    }
  };

  // 步骤1, 注册指令loading ,就是我们使用的时候 v-loading
  Vue.directive('loading', {
    // el 指令所绑定的元素,可以用来直接操作 DOM
    /**
    * binding:一个对象,包含以下 property:
      * name:指令名,不包括 v- 前缀。 loading
      * value:指令的绑定值,例如:v-loading="1 > 2" 中,绑定值为 false。
      * oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
      * expression:字符串形式的指令表达式。例如 v-loading="1 > 2" 中,表达式为 "1 > 2"。
      * arg:传给指令的参数,可选。例如 v-loading:foo 中,参数为 "foo"。
      * modifiers:一个包含修饰符的对象。例如:v-loading.fullscreen.lock="fullscreenLoading" 中,修饰符对象为 { fullscreen: true, lock: true }。
    */
    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');

      // context: Component | void; // rendered in this component's scope; see more to vnode详情 https://github.com/vuejs/vue/blob/dev/src/core/vdom/vnode.js  Component 详情 https://github.com/vuejs/vue/blob/e7cf0634f59d0edd472c48792e371b91a8c55af7/flow/component.js#L5
      const vm = vnode.context;

      // 新增一个loading蒙层,挂载到一个新生成的div节点上
      // binding.modifiers.fullscreen 判断修饰符有没有全屏
      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 = {};

      // 如果是指令绑定的值true就执行toggleLoading
      binding.value && toggleLoading(el, binding);
    },

    // 所在组件的 VNode 更新时调用
    update: function(el, binding) {
      // 重新设置文案
      el.instance.setText(el.getAttribute('element-loading-text'));
      // 新老值不一样的时候,才调用,防止重复调用
      if (binding.oldValue !== binding.value) {
        toggleLoading(el, binding);
      }
    },

    // 只调用一次,指令与元素解绑时调用。
    unbind: function(el, binding) {
      // 实例被插入过,同时有mask和mask父节点,就移除mask节点。 
      // 调用toggleLoading方法,走afterLeave,做移除class等清理工作
      if (el.domInserted) {
        el.mask &&
        el.mask.parentNode &&
        el.mask.parentNode.removeChild(el.mask);
        toggleLoading(el, { value: false, modifiers: binding.modifiers });
      }
      // el实例存在的话手动调用摧毁
      el.instance && el.instance.$destroy();
    }
  });
};

export default loadingDirective;

Copy the code