ElementUI 、AntDesign 、IVIew三大组件库z-index层级管理方案对比总结

ElementUI

elementUI将弹窗层级管理收敛到了一个入口PopupManager中,涉及zIndex层级的弹窗组件实例都需要注册到PopupManager中。源码传送门

// element/src/utils/popup/popup-manager.js
import {
    
     addClass, removeClass } from 'element-ui/src/utils/dom';

// 是否有模态层
let hasModal = false;
// 是否有初始zIndex
let hasInitZIndex = false;
// z-index值
let zIndex;

// 获取模态层
const getModal = function() {
    
    
  let modalDom = PopupManager.modalDom;
  if (modalDom) {
    
    
    hasModal = true;
  } else {
    
    
    hasModal = false;
    modalDom = document.createElement('div');
    PopupManager.modalDom = modalDom;

    modalDom.addEventListener('touchmove', function(event) {
    
    
      event.preventDefault();
      event.stopPropagation();
    });

    modalDom.addEventListener('click', function() {
    
    
      PopupManager.doOnModalClick && PopupManager.doOnModalClick();
    });
  }

  return modalDom;
};

// 组件实例集合
const instances = {
    
    };

// 弹窗管理器
const PopupManager = {
    
    
  // 模态消失渐退
  modalFade: true,

  getInstance: function(id) {
    
    
    return instances[id];
  },

  register: function(id, instance) {
    
    
    if (id && instance) {
    
    
      instances[id] = instance;
    }
  },

  deregister: function(id) {
    
    
    if (id) {
    
    
      instances[id] = null;
      delete instances[id];
    }
  },

  nextZIndex: function() {
    
    
    return PopupManager.zIndex++;
  },

  modalStack: [],

  doOnModalClick: function() {
    
    
    const topItem = PopupManager.modalStack[PopupManager.modalStack.length - 1];
    if (!topItem) return;

    const instance = PopupManager.getInstance(topItem.id);
    if (instance && instance.closeOnClickModal) {
    
    
      instance.close();
    }
  },

  // 打开模态层
  openModal: function(id, zIndex, dom, modalClass, modalFade) {
    
    
    if (!id || zIndex === undefined) return;
    this.modalFade = modalFade;

    const modalStack = this.modalStack;

    for (let i = 0, j = modalStack.length; i < j; i++) {
    
    
      const item = modalStack[i];
      if (item.id === id) {
    
    
        return;
      }
    }

    const modalDom = getModal();

    addClass(modalDom, 'v-modal');
    if (this.modalFade && !hasModal) {
    
    
      addClass(modalDom, 'v-modal-enter');
    }
    if (modalClass) {
    
    
      let classArr = modalClass.trim().split(/\s+/);
      classArr.forEach(item => addClass(modalDom, item));
    }
    setTimeout(() => {
    
    
      removeClass(modalDom, 'v-modal-enter');
    }, 200);

    if (dom && dom.parentNode && dom.parentNode.nodeType !== 11) {
    
    
      dom.parentNode.appendChild(modalDom);
    } else {
    
    
      document.body.appendChild(modalDom);
    }

    if (zIndex) {
    
    
      modalDom.style.zIndex = zIndex;
    }
    modalDom.tabIndex = 0;
    modalDom.style.display = '';

    this.modalStack.push({
    
     id: id, zIndex: zIndex, modalClass: modalClass });
  },
  
  // 关闭模态层
  closeModal: function(id) {
    
    
    const modalStack = this.modalStack;
    const modalDom = getModal();

    if (modalStack.length > 0) {
    
    
      const topItem = modalStack[modalStack.length - 1];
      if (topItem.id === id) {
    
    
        if (topItem.modalClass) {
    
    
          let classArr = topItem.modalClass.trim().split(/\s+/);
          classArr.forEach(item => removeClass(modalDom, item));
        }

        modalStack.pop();
        if (modalStack.length > 0) {
    
    
          modalDom.style.zIndex = modalStack[modalStack.length - 1].zIndex;
        }
      } else {
    
    
        for (let i = modalStack.length - 1; i >= 0; i--) {
    
    
          if (modalStack[i].id === id) {
    
    
            modalStack.splice(i, 1);
            break;
          }
        }
      }
    }

    if (modalStack.length === 0) {
    
    
      if (this.modalFade) {
    
    
        addClass(modalDom, 'v-modal-leave');
      }
      setTimeout(() => {
    
    
        if (modalStack.length === 0) {
    
    
          if (modalDom.parentNode) modalDom.parentNode.removeChild(modalDom);
          modalDom.style.display = 'none';
          PopupManager.modalDom = undefined;
        }
        removeClass(modalDom, 'v-modal-leave');
      }, 200);
    }
  }
};

// 代理zIndex
Object.defineProperty(PopupManager, 'zIndex', {
    
    
  configurable: true,
  get() {
    
    
    if (!hasInitZIndex) {
    
    
      zIndex = zIndex || 2000;
      hasInitZIndex = true;
    }
    return zIndex;
  },
  set(value) {
    
    
    zIndex = value;
  }
});

// 获取最高层的弹窗
const getTopPopup = function() {
    
    
  if (PopupManager.modalStack.length > 0) {
    
    
    const topPopup = PopupManager.modalStack[PopupManager.modalStack.length - 1];
    if (!topPopup) return;
    const instance = PopupManager.getInstance(topPopup.id);

    return instance;
  }
};

// 处理esc按键
window.addEventListener('keydown', function(event) {
    
    
  if (event.keyCode === 27) {
    
    
    const topPopup = getTopPopup();

    if (topPopup && topPopup.closeOnPressEscape) {
    
    
      topPopup.handleClose
        ? topPopup.handleClose()
        : (topPopup.handleAction ? topPopup.handleAction('cancel') : topPopup.close());
    }
  }
});

export default PopupManager;

AntDesign

AntDesign的层级管理方案,利用了less预处理语言的特性,根据组件类型声明基准zIndex,不同类型组件之间的基准值相差至少10,同时组件内部基于基准值维护各自的zIndex。源码传送门

// ant-design/components/style/themes/default.less
/* z-index列表, 按值从小到大排列 */
@zindex-badge: auto;
@zindex-table-fixed: 2;
@zindex-affix: 10;
@zindex-back-top: 10;
@zindex-picker-panel: 10;
@zindex-popup-close: 10;
@zindex-modal: 1000;
@zindex-modal-mask: 1000;
@zindex-message: 1010;
@zindex-notification: 1010;
@zindex-popover: 1030;
@zindex-dropdown: 1050;
@zindex-picker: 1050;
@zindex-popoconfirm: 1060;
@zindex-tooltip: 1070;
@zindex-image: 1080;

以Table组件为例:
// ant-design/components/table/style/index.less
@import '../../style/themes/index';
 
@table-sticky-zindex: (@zindex-table-fixed + 1);
 
&-summary {
    position: relative;
    z-index: @zindex-table-fixed;
    background: @table-bg;
}

&-cell-fix-left,
&-cell-fix-right {
    position: -webkit-sticky !important;
    position: sticky !important;
    z-index: @zindex-table-fixed;
    background: @table-bg;
}

&-sticky {
    &-holder {
        position: sticky;
        z-index: @table-sticky-zindex;
        background: @component-background;
    }
}

IVIew

IView的方案和ElementUI类似,但是看上去更简单直接,全局维护一个transferIndex值,每个组件只要使用了一次transferIndex,就主动调用方法transferIncrease使得transferIndex的值+1。源码传送门

// iview/src/utils/transfer-queue.js
let transferIndex = 0;

function transferIncrease() {
    
    
    transferIndex++;
}

export {
    
    transferIndex, transferIncrease};

下面以poptip组件为例(已移除无关代码)。其默认基础zIndex为1060,实际zIndex为1060 + transferIndex,当每个Poptip组件实例生成时,获取当前transferIndex,并调用方法transferIncrease。源码传送门

// iview/src/components/poptip/poptip.vue
import {
    
     transferIndex, transferIncrease } from '../../utils/transfer-queue';

export default {
    
    
    data() {
    
    
        return {
    
    
            tIndex: this.handleGetIndex()
        }
    },
    computed: {
    
    
        styles() {
    
    
            let style = {
    
    };
    
            if (this.transfer) style['z-index'] = 1060 + this.tIndex;
    
            return style;
        }
    },
    methods: {
    
    
        handleGetIndex () {
    
    
            transferIncrease();
            return transferIndex;
        }
    }
}

对比总结

库名 方法 优点 缺点
ElementUI 统一入口注册组件,在内部管理层级 统一管理,层级递增,不易紊乱 只针对弹窗层级管理
AntDesign 根据类型划分基准层级,组件内部维护层级关系 分层设计,隔离性高 基准层级的设定依靠经验值
IView 由各个组件维护全局zIndex 颗粒度较细,针对组件定制化 层级维护较为零散,排查问题时比较麻烦

猜你喜欢

转载自blog.csdn.net/sinat_36521655/article/details/120051181
今日推荐