ElementUI
elementUI将弹窗层级管理收敛到了一个入口PopupManager中,涉及zIndex层级的弹窗组件实例都需要注册到PopupManager中。源码传送门
import { addClass, removeClass } from 'element-ui/src/utils/dom';
let hasModal = false;
let hasInitZIndex = false;
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);
}
}
};
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;
}
};
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。源码传送门
let transferIndex = 0;
function transferIncrease() {
transferIndex++;
}
export {transferIndex, transferIncrease};
下面以poptip组件为例(已移除无关代码)。其默认基础zIndex为1060,实际zIndex为1060 + transferIndex,当每个Poptip组件实例生成时,获取当前transferIndex,并调用方法transferIncrease。源码传送门
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 | 颗粒度较细,针对组件定制化 | 层级维护较为零散,排查问题时比较麻烦 |
|