zoukankan      html  css  js  c++  java
  • element loading源码

    本文参考https://segmentfault.com/a/1190000018535744

    loading/index.js

    import directive from './src/directive';
    import service from './src/index';
    
    export default {
      // 这里为什么有个 install 呢
      // 当你使用单组件单注册的时候就会调用这里了
      // 效果和下面一样,挂载指令,挂载服务
      install (Vue) {
        Vue.use(directive);
        Vue.prototype.$loading = service;
      },
      // 就是上面的 Loading.directive
      directive,
      // 就是上面的 Loading.service
      service
    };
    

    loading/src/loading.vue

    <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 {
            // 显示在加载图标下方的加载文案    string
            text: null,
            // 自定义加载图标类名    string
            spinner: null,
            // 遮罩背景色    string
            background: null,
            // 同 v-loading 指令中的 fullscreen 修饰符    boolean
            fullscreen: true,
            // 是否显示
            visible: false,
            // customClass    Loading 的自定义类名    string
            customClass: ''
          };
        },
    
        methods: {
          // loading结束后触发
          handleAfterLeave() {
            this.$emit('after-leave');
          },
          setText(text) {
            this.text = text;
          }
        }
      };
    </script>

    loading/src/directive.js

    // v-loading 指令解析
    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';
    const Mask = Vue.extend(Loading);
    
    const loadingDirective = {};
    // 还记得 Vue.use() 的使用方法么?若传入的是对象,该对象需要一个 install 属性
    loadingDirective.install = Vue => {
      if (Vue.prototype.$isServer) return;
      // 这里处理显示、消失 loading
      const toggleLoading = (el, binding) => {
        if (binding.value) {
          Vue.nextTick(() => {
            if (binding.modifiers.fullscreen) {
              el.originalPosition = getStyle(document.body, 'position');
              el.originalOverflow = getStyle(document.body, 'overflow');
              el.maskStyle.zIndex = PopupManager.nextZIndex();
    
              addClass(el.mask, 'is-fullscreen');
              insertDom(document.body, el, binding);
            } else {
              removeClass(el.mask, 'is-fullscreen');
    
              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 {
          // 不然则将其设为不可见
          // 从上往下读我们是第一次看到 visible 属性
          // 别急,往下看,这个属性可以其实就是单文件 loading.vue 里面的
          // data() { return { visible: false } }
          afterLeave(el.instance, _ => {
            el.domVisible = false;
            const target = binding.modifiers.fullscreen || binding.modifiers.body
              ? document.body
              : el;
            removeClass(target, 'el-loading-parent--relative');
            removeClass(target, 'el-loading-parent--hidden');
            el.instance.hiding = false;
          }, 300, true);
          el.instance.visible = false;
          el.instance.hiding = true;
        }
      };
      // 插入dom
      const insertDom = (parent, el, binding) => {
    
        if (!el.domVisible && getStyle(el, 'display') !== 'none' && getStyle(el, 'visibility') !== 'hidden') {
          Object.keys(el.maskStyle).forEach(property => {
            el.mask.style[property] = el.maskStyle[property];
          });
    
          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;
          // appendChild 添加的元素若为同一个,则不会重复添加
          // 我们 el.mask 所指的为同一个 dom 元素
          // 因为我们只在 bind 的时候给 el.mask 赋值
          // 并且在组件存在期间,bind 只会调用一次
          parent.appendChild(el.mask);
          Vue.nextTick(() => {
            if (el.instance.hiding) {
              el.instance.$emit('after-leave');
            } else {
              // 将 loading 设为可见
              el.instance.visible = true;
            }
          });
          el.domInserted = true;
        }
      };
      // 在此注册 directive 指令
      Vue.directive('loading', {
        bind: function (el, binding, vnode) {
          // 创建一个子组件,这里和 new Vue(options) 类似
          // 返回一个组件实例
          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 不按照 Vue 官方建议传函数进去呢?
            // 其实这里两者皆可
            // 稍微做一点延展好了,在 Vue 源码里面,data 是延迟求值的
            // 贴一点 Vue 源码上来
            // return function mergedInstanceDataFn() {
            //   let instanceData = typeof childVal === 'function'
            //     ? childVal.call(vm, vm)
            //     : childVal;
            //   let defaultData = typeof parentVal === 'function'
            //     ? parentVal.call(vm, vm)
            //     : parentVal;
            //   if (instanceData) {
            //     return mergeData(instanceData, defaultData)
            //   } else {
            //     return defaultData
            //   }
            // }
            // instanceData 就是我们现在传入的 data: {}
            // defaultData 就是我们 loading.vue 里面的 data() {}
            // 看了这段代码应该就不难理解为什么可以传对象进去了
            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 上
          // 在 directive 的文档中建议
          // 应该保证除了 el 之外其他参数(binding、vnode)都是只读的
          el.instance = mask;
          // 挂载 dom
          el.mask = mask.$el;
          el.maskStyle = {};
          // 若 binding 的值为 truthy 运行 toogleLoading
          binding.value && toggleLoading(el, binding);
        },
    
        update: function (el, binding) {
          el.instance.setText(el.getAttribute('element-loading-text'));
          if (binding.oldValue !== binding.value) {
            // 若旧不等于新值得时候(一般都是由 true 切换为 false 的时候)
            toggleLoading(el, binding);
          }
        },
    
        unbind: function (el, binding) {
          if (el.domInserted) {
            // 移除dom
            el.mask &&
              el.mask.parentNode &&
              el.mask.parentNode.removeChild(el.mask);
            toggleLoading(el, { value: false, modifiers: binding.modifiers });
          }
          el.instance && el.instance.$destroy();
        }
      });
    };
    
    export default loadingDirective;

    loading/src/index.js

    // loading服务模式
    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';
    
    // 和指令模式一样,创建实例构造器
    const LoadingConstructor = Vue.extend(loadingVue);
    
    const defaults = {
      text: null,
      fullscreen: true,
      body: false,
      lock: false,
      customClass: ''
    };
    // 定义变量,若使用的是全屏 loading 那就要保证全局的 loading 只有一个
    let fullscreenLoading;
    
    LoadingConstructor.prototype.originalPosition = '';
    LoadingConstructor.prototype.originalOverflow = '';
    
    // 这里可以看到和指令模式不同的地方
    // 在调用了 close 之后就会移除该元素并销毁组件
    LoadingConstructor.prototype.close = function () {
      if (this.fullscreen) {
        fullscreenLoading = undefined;
      }
      afterLeave(this, _ => {
        const target = this.fullscreen || this.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) {
        instance.originalPosition = getStyle(document.body, 'position');
        instance.originalOverflow = getStyle(document.body, 'overflow');
        maskStyle.zIndex = PopupManager.nextZIndex();
      } else if (options.body) {
        instance.originalPosition = getStyle(document.body, 'position');
        ['top', 'left'].forEach(property => {
          let scroll = property === 'top' ? 'scrollTop' : 'scrollLeft';
          maskStyle[property] = options.target.getBoundingClientRect()[property] +
            document.body[scroll] +
            document.documentElement[scroll] +
            'px';
        });
        ['height', 'width'].forEach(property => {
          maskStyle[property] = options.target.getBoundingClientRect()[property] + 'px';
        });
      } else {
        instance.originalPosition = getStyle(parent, 'position');
      }
      Object.keys(maskStyle).forEach(property => {
        instance.$el.style[property] = maskStyle[property];
      });
    };
    
    const Loading = (options = {}) => {
      if (Vue.prototype.$isServer) return;
      options = merge({}, defaults, options);
      // Loading 需要覆盖的 DOM 节点。可传入一个 DOM 对象或字符串;若传入字符串,则会将其作为参数传入 document.querySelector以获取到对应 DOM 节点
      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;
      }
      // 若调用 loading 的时候传入了 fullscreen 并且 fullscreenLoading 不为 falsy
      // fullscreenLoading 只会在下面赋值,并且指向了 loading 实例
      if (options.fullscreen && fullscreenLoading) {
        return fullscreenLoading;
      }
    
      let parent = options.body ? document.body : options.target;
      // 这里就不用说了吧,和指令中是一样的
      let instance = new LoadingConstructor({
        el: document.createElement('div'),
        data: options
      });
    
      addStyle(options, parent, instance);
      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);
      Vue.nextTick(() => {
        instance.visible = true;
      });
      // 若传入了 fullscreen 参数,则将实例存储
      if (options.fullscreen) {
        // 需要注意的是,以服务的方式调用的全屏 Loading 是单例的:若在前一个全屏 Loading 关闭前再次调用全屏 Loading,并不会创建一个新的 Loading 实例,而是返回现有全屏 Loading 的实例
        fullscreenLoading = instance; 
      }
      return instance;
    };
    
    export default Loading;
  • 相关阅读:
    逻辑地址、线性地址、物理地址
    查找已知字符串子串
    替换字符串中的空格为%20
    资本的奥秘
    net::ERR_CONNECTION_RESET的处理方法
    SQL Server数据库从低版本向高版本复制数据库
    中式思维的五大逻辑缺陷(转)
    1年PK12年,中国式教育完败(转载)
    有关衣服的想法
    jquery邮箱自动补全
  • 原文地址:https://www.cnblogs.com/wsk1576025821/p/10955554.html
Copyright © 2011-2022 走看看