element-ui 源码分析-工具篇:popup
Popup
是一个层级关系(z-index
)管理工具类,主要用于管理组件的层级关系,例如message-box
,dialog
组件。在element-ui源码中,主要分为两个popup-manger.js
和popup mixin
这两部分,我们先看下popup mixin
这个文件。
Popup
此阶段不需要深究PopupManager的内部原理,直接调用PopupManager对象的方法即可。为了方便沟通,将引入了popup-mixin的单文件vue组件命名。定义为为弹窗组件,弹窗的灰色蒙层命名为modalDompopup.js
是一个mixin混入,功能清单如下:
- 引入
popupManger
beforeMount
周期时,调用PopupManager
对象的注册方法beforeDestroy
周期中,调用PopupManager
对象的注销方法openModa
方法,设置弹窗组件的z-index
,调用PopupManager.openModal
方法closeModal
方法,调用PopupManager.closeModal
方法
为了方便大家阅读,下面列出的代码相对源码来说有所删减,只保留了核心功能的代码
import merge from '../merge' import PopupManager from './popup-manager' let idSeed = 1 export default { props: { visible: { type: Boolean, default: false }, modalAppendToBody: { type: Boolean, default: false }, closeOnPressEscape: { type: Boolean, default: false }, closeOnClickModal: { type: Boolean, default: false } }, beforeMount() { this._popupId = 'popup-' + idSeed++ PopupManager.register(this._popupId, this) }, beforeDestroy() { PopupManager.register(this._popupId) PopupManager.closeModal(this._popupId) }, data() { return { _popupId: null } }, watch: { // 对于watch选项混入时,mixin内的和弹窗组件内的代码都会执行 visible(val) { if (val) { if (this._opening) return // 判断是否是第一次渲染 if (!this.rendered) { this.rendered = true this.$nextTick(() => { this.open() }) } else { this.open() } } } }, methods: { open(options) { if (!this.rendered) { this.rendered = true } // 选项合并 const props = merge({}, options, this.$props) this.doOpen(props) }, doOpen(props) { if(this.opended) return this._opening = true const dom = this.$el PopupManager.openModal(this._popupId, PopupManager.nextZIndex(), this.modalAppendToBody ? undefined : dom) // 确保z-index属性能生效,z-index只能在position属性值为relative或absolute或fixed的元素上有效。 if (getComputedStyle(dom).position === 'static') { dom.style.position = 'absolute' } // 设置组件的z-index,保证组件的层级在modalDom之上 dom.style.zIndex = PopupManager.nextZIndex() this.opened = true // 执行onopen回调函数 this.onOpen && this.onOpen() this.doAfterOpen() }, doAfterOpen() { this._opening = false }, close() { this.doClose() }, doClose() { this._closing = true this.onClose && this.onClose() this.opened = false this.doAfterClose() }, doAfterClose() { // 关闭modal PopupManager.closeModal(this._popupId) this._closing = false } } } export { PopupManager }
PopupManager
现在我们再看下popupManager的源码,同样为了方便阅读,对部分非核心代码做了删减,阅读时请配合此demo一起观看:[element-ui嵌套dialog] (https://jsfiddle.net/api/post...)
/* eslint-disable */ import Vue from 'vue' import { addClass, removeClass } from 'element-ui/src/utils/dom' let hasInitZIndex = false let zIndex /** * 返回modalDom元素 * 第一次调用时,会创建一个div,并绑定click事件,并将此div赋值给PopupManager的modalDom属性 * 第二次调用时直接返回PopupManager.modalDom */ const getModal = function() { if (Vue.prototype.$isServer) return let modalDom = PopupManager.modalDom if (!modalDom) { 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 = { // 是否开启modal淡入淡出动画 modalFade: true, // 注册弹窗组件 register: function(id, instance) { if (id && instance) { instances[id] = instance } }, // 注销弹窗组件 deregister: function(id) { if (id) { instances[id] = null delete instances[id] } }, // 根据id获取弹窗组件 getInstance: function(id) { return instances[id] }, nextZIndex: function() { return PopupManager.zIndex++ }, // 虚拟的modal蒙层数组,每一个弹窗组件对应一个虚拟的modal蒙层,实际上在页面中只存在一个modal蒙层,所有的弹窗组件共用一个蒙层即可,可以参考嵌套弹窗demo modalStack: [], /** * 页面中添加modalDom,在上面的额popup-mixin的doOpen方法里面,就调用了PopupManager.openModal方法 * 弹窗组件调用过openModal方法后,PopupManager会将弹窗组件(id,zIndex)push进modalStack中进行管理 * 弹窗窗组件调用PopupManager.closeModal方法后,PopupManager会将弹窗组件从modalStack中删除 */ openModal: function(id, zIndex, dom) { const modalStack = this.modalStack for (let i = 0, j = modalStack.length; i < j; i++) { const item = modalStack[i] if (item.id === id) { return } } if (!id || !zIndex) return // 调用getModal方法,获取到真实的modalDom元素 var modalDom = getModal() addClass(modalDom, 'v-modal') // 根据传入的dom元素,判断modalDom应该插入的节点 if (dom && dom.parentNode && dom.parentNode.nodeType !== 11) { dom.parentNode.appendChild(modalDom) } else { document.body.appendChild(modalDom) } // 设置modalDom的zIndex, // 回到popup-mixin代码的openModa处,可以发现传入的zInxex值是PopupManager.nextZIndex(),然后弹窗组件又给自己设置了zindex,dom.style.zIndex = PopupManager.nextZIndex() //这样就能确保弹窗组件的zIndex比modalDom高,展示在modalDom之上 modalDom.style.zIndex = zIndex modalDom.style.display = '' this.modalStack.push({ id, zIndex }) }, // 当有多个弹窗组件时,只有最上面的弹窗组件层级关系是在modalDom上,所以此时点击modal,应该去执行最上层弹窗组件的回调方法 doOnModalClick: function() { var topItem = this.modalStack[this.modalStack.length - 1] if (topItem) { const instance = PopupManager.getInstance(topItem.id) if (instance && instance.closeOnClickModal) { instance.close() } } }, // 关闭弹窗,阅读之前,请先看一下关闭嵌套dialog时,各个弹窗与modalDom的表现 closeModal: function(id) { const modalStack = this.modalStack const modalDom = getModal() var popup = this.getInstance(id) //modalStack中删除对应的弹窗组件 if (modalStack.length > 0) { const topItem = modalStack[modalStack.length - 1] if (topItem.id === id) { modalStack.pop() if (modalStack.length > 0) { /* 如果是最上层的弹窗组件,且下面还存在其他的弹窗组件,则重新设置modalDOm的zindex,modalStack中保存的zIndex并不是弹窗组件dom的Zindex,而是执行PopupManager.openModal方法时传入的zindex,弹窗组件dom的Zindex是在执行PopupManager.openModal方法后再设置的*/ 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 } } } } // 如果没有其他弹窗组件了,则隐藏modalDom if (this.modalStack.length === 0) { if (modalDom.parentNode) modalDom.parentNode.removeChild(modalDom) modalDom.style.display = 'none' PopupManager.modalDom = undefined } } } Object.defineProperty(PopupManager, 'zIndex', { configurable: true, get() { if (!hasInitZIndex) { hasInitZIndex = true zIndex = zIndex || 2000 } 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 } } if (!Vue.prototype.$isServer) { // handle `esc` key when the popup is shown 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
后记
出于减少读者阅读量的原因,本文列出的代码只包括核心功能,有条件的同学可以直接阅读源码,看一下官方是如何实现lockOnScroll
,delayOpen
等相关功能的
相关推荐
瓜牛呱呱 2020-11-12
柳木木的IT 2020-11-04
yifouhu 2020-11-02
lei0 2020-11-02
源码zanqunet 2020-10-28
源码zanqunet 2020-10-26
一叶梧桐 2020-10-14
码代码的陈同学 2020-10-14
lukezhong 2020-10-14
lzzyok 2020-10-10
anchongnanzi 2020-09-21
clh0 2020-09-18
changcongying 2020-09-17
星辰大海的路上 2020-09-13
abfdada 2020-08-26
mzy000 2020-08-24
shenlanse 2020-08-18
zhujiangtaotaise 2020-08-18
xiemanR 2020-08-17