From ade8a97d6db29b30f5fb53af16eb99716f75cd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Mon, 16 Oct 2017 16:24:10 +0800 Subject: [PATCH] [bugfix] Popup modal display error --- packages/mixins/popup/index.js | 97 ++++---------- packages/mixins/popup/popup-context.js | 32 +---- packages/mixins/popup/popup-manager.js | 149 ++++++--------------- packages/popup/index.vue | 32 +---- packages/vant-css/src/common/animation.css | 9 ++ packages/vant-css/src/popup.css | 6 +- test/unit/specs/popup.spec.js | 11 +- 7 files changed, 100 insertions(+), 236 deletions(-) diff --git a/packages/mixins/popup/index.js b/packages/mixins/popup/index.js index 52317cbe2..208833abd 100644 --- a/packages/mixins/popup/index.js +++ b/packages/mixins/popup/index.js @@ -1,9 +1,9 @@ -import PopupManager from './popup-manager'; -import PopupContext from './popup-context'; +import manager from './popup-manager'; +import context from './popup-context'; export default { props: { - // popup当前显示状态 + // popup 当前显示状态 value: { type: Boolean, default: false @@ -13,16 +13,13 @@ export default { type: Boolean, default: false }, - /** - * 点击遮罩层是否关闭popup - */ + // 点击遮罩层是否关闭 popup closeOnClickOverlay: { type: Boolean, default: false }, zIndex: [String, Number], - // popup滚动时是否body内容也滚动 - // 默认为不滚动 + // popup 滚动时是否禁用 body 滚动 lockOnScroll: { type: Boolean, default: true @@ -36,27 +33,18 @@ export default { watch: { value(val) { - if (val) { - if (this.opening) return; - this.open(); - } else { - if (this.closing) return; - this.close(); - } + this[val ? 'open' : 'close'](); } }, beforeMount() { - this._popupId = 'popup-' + PopupContext.plusKeyByOne('idSeed'); - PopupManager.register(this._popupId, this); + this._popupId = 'popup-' + context.plusKeyByOne('idSeed'); + context.instances[this._popupId] = this; }, data() { return { - opening: false, opened: false, - closing: false, - bodyOverflow: null, pos: { x: 0, y: 0 @@ -71,6 +59,7 @@ export default { y: e.touches[0].clientY }; }, + watchTouchMove(e) { const pos = this.pos; const dx = e.touches[0].clientX - pos.x; @@ -95,47 +84,28 @@ export default { e.stopPropagation(); } }, - /** - * 显示popup - */ - open() { - /* istanbul ignore if */ - if (this.$isServer) return; - if (this.opened) return; - this.opening = true; + open() { + if (this.opened || this.$isServer) { + return; + } this.$emit('input', true); - const zIndex = this.zIndex; - - // 如果属性中传入了`zIndex`,则覆盖`popupContext`中对应的`zIndex` - if (zIndex) { - PopupContext.setContext('zIndex', zIndex); + // 如果属性中传入了`zIndex`,则覆盖`context`中对应的`zIndex` + if (this.zIndex !== undefined) { + context.zIndex = this.zIndex; } - // 如果显示遮罩层 if (this.overlay) { - if (this.closing) { - PopupManager.closeModal(this._popupId); - this.closing = false; - } - PopupManager.openModal(this._popupId, PopupManager.nextZIndex(), this.$el); - - // 如果滚动时需要锁定 + manager.openModal(this._popupId, context.plusKeyByOne('zIndex'), this.$el); if (this.lockOnScroll) { - // 将原来的`bodyOverflow`存起来 - if (!this.bodyOverflow) { - this.bodyOverflow = document.body.style.overflow; - } - - document.body.style.overflow = 'hidden'; + document.body.classList.add('van-overflow-hidden'); } } - this.$el.style.zIndex = PopupManager.nextZIndex(); + this.$el.style.zIndex = context.plusKeyByOne('zIndex'); this.opened = true; - this.opening = false; if (this.preventScroll) { document.addEventListener('touchstart', this.recordPosition, false); @@ -143,23 +113,15 @@ export default { } }, - /** - * 关闭popup - */ close() { - if (this.closing) return; - - this.closing = true; + if (!this.opened || this.$isServer) { + return; + } this.$emit('input', false); if (this.lockOnScroll) { - setTimeout(() => { - if (this.overlay && this.bodyOverflow !== 'hidden') { - document.body.style.overflow = this.bodyOverflow; - } - this.bodyOverflow = null; - }, 200); + document.body.classList.remove('van-overflow-hidden'); } this.opened = false; @@ -167,8 +129,7 @@ export default { }, doAfterClose() { - this.closing = false; - PopupManager.closeModal(this._popupId); + manager.closeModal(this._popupId); if (this.preventScroll) { document.removeEventListener('touchstart', this.recordPosition, false); @@ -178,12 +139,10 @@ export default { }, beforeDestroy() { - PopupManager.deregister(this._popupId); - PopupManager.closeModal(this._popupId); - - if (this.overlay && this.bodyOverflow !== null && this.bodyOverflow !== 'hidden') { - document.body.style.overflow = this.bodyOverflow; + context.instances[this._popupId] = null; + manager.closeModal(this._popupId); + if (this.lockOnScroll) { + document.body.classList.remove('van-overflow-hidden'); } - this.bodyOverflow = null; } }; diff --git a/packages/mixins/popup/popup-context.js b/packages/mixins/popup/popup-context.js index 0e0588b7b..81035f451 100644 --- a/packages/mixins/popup/popup-context.js +++ b/packages/mixins/popup/popup-context.js @@ -1,35 +1,15 @@ -import Vue from 'vue'; - -const _global = Vue.prototype.$isServer ? global : window; - -const DEFAULT_CONTEXT = { +const PopupContext = { idSeed: 1, zIndex: 2000, - hasModal: false, instances: {}, - modalStack: [] -}; - -if (!_global.popupContext) { - _global.popupContext = { - ...DEFAULT_CONTEXT - }; -} - -const PopupContext = { - getContext(key) { - return _global.popupContext[key]; - }, - - setContext(key, value) { - _global.popupContext[key] = value; - }, + modalStack: [], plusKeyByOne(key) { - const oldVal = +_global.popupContext[key]; - _global.popupContext[key] = oldVal + 1; + return this[key]++; + }, - return oldVal; + get topModal() { + return this.modalStack[this.modalStack.length - 1]; } }; diff --git a/packages/mixins/popup/popup-manager.js b/packages/mixins/popup/popup-manager.js index 4c71dfc99..62ebc8c6c 100644 --- a/packages/mixins/popup/popup-manager.js +++ b/packages/mixins/popup/popup-manager.js @@ -1,133 +1,68 @@ -import Vue from 'vue'; -import PopupContext from './popup-context'; - -const getModal = function() { - if (Vue.prototype.$isServer) return; - let modalDom = PopupContext.getContext('modalDom'); - - if (modalDom) { - PopupContext.setContext('hasModal', true); - } else { - PopupContext.setContext('hasModal', false); - - modalDom = document.createElement('div'); - PopupContext.setContext('modalDom', modalDom); - - modalDom.addEventListener('touchmove', function(event) { - event.preventDefault(); - event.stopPropagation(); - }); - - modalDom.addEventListener('click', function() { - PopupManager.handleOverlayClick && PopupManager.handleOverlayClick(); - }); - } - - return modalDom; -}; +import context from './popup-context'; const PopupManager = { - nextZIndex() { - return PopupContext.plusKeyByOne('zIndex'); - }, + getModal() { + let { modal } = context; - getInstance(id) { - return PopupContext.getContext('instances')[id]; - }, + if (!modal) { + modal = document.createElement('div'); + modal.classList.add('van-modal'); + modal.addEventListener('touchmove', event => { + event.preventDefault(); + event.stopPropagation(); + }); + modal.addEventListener('click', () => { + PopupManager.handleOverlayClick(); + }); - register(id, instance) { - if (id && instance) { - const instances = PopupContext.getContext('instances'); - instances[id] = instance; + context.modal = modal; } + + return modal; }, - deregister(id) { - if (id) { - const instances = PopupContext.getContext('instances'); - instances[id] = null; - delete instances[id]; - } - }, - - /** - * 遮罩层点击回调,`closeOnClickOverlay`为`true`时会关闭当前`popup` - */ + // close popup when click modal && closeOnClickOverlay is true handleOverlayClick() { - const modalStack = PopupContext.getContext('modalStack'); - const topModal = modalStack[modalStack.length - 1]; - if (!topModal) return; - - const instance = PopupManager.getInstance(topModal.id); - if (instance && instance.closeOnClickOverlay) { - instance.close(); + const { topModal } = context; + if (topModal) { + const instance = context.instances[topModal.id]; + if (instance && instance.closeOnClickOverlay) { + instance.close(); + } } }, openModal(id, zIndex, dom) { - if (!id || zIndex === undefined) return; + const { modalStack } = context; + const exist = modalStack.some(item => item.id === id); - const modalStack = PopupContext.getContext('modalStack'); + if (!exist) { + const modal = this.getModal(); + modal.style.zIndex = zIndex; - for (let i = 0, len = modalStack.length; i < len; i++) { - const item = modalStack[i]; - if (item.id === id) { - return; - } - } - - const modalDom = getModal(); - - modalDom.classList.add('van-modal'); - - let domParentNode; - if (dom && dom.parentNode && dom.parentNode.nodeType !== 11) { - domParentNode = dom.parentNode; - } else { - domParentNode = document.body; - } - domParentNode.appendChild(modalDom); - - if (zIndex) { - modalDom.style.zIndex = zIndex; - } - modalDom.style.display = ''; - - modalStack.push({ id: id, zIndex: zIndex, parentNode: domParentNode }); + const parentNode = dom && dom.parentNode && dom.parentNode.nodeType !== 11 ? dom.parentNode : document.body; + parentNode.appendChild(modal); + modalStack.push({ id, zIndex, parentNode }); + }; }, closeModal(id) { - const modalStack = PopupContext.getContext('modalStack'); - const modalDom = getModal(); + const { modalStack } = context; - if (modalStack.length > 0) { - const topItem = modalStack[modalStack.length - 1]; - if (topItem.id === id) { + if (modalStack.length) { + if (context.topModal.id === id) { + const modal = this.getModal(); modalStack.pop(); - if (modalStack.length > 0) { - modalDom.style.zIndex = modalStack[modalStack.length - 1].zIndex; - modalDom.parentNode.removeChild(modalDom); - const currModalParent = modalStack[0].parentNode; - currModalParent && currModalParent.appendChild(modalDom); + modal.parentNode.removeChild(modal); + if (modalStack.length) { + const { topModal } = context; + modal.style.zIndex = topModal.zIndex; + topModal.parentNode.appendChild(modal); } } else { - for (let i = modalStack.length - 1; i >= 0; i--) { - if (modalStack[i].id === id) { - modalStack.splice(i, 1); - break; - } - } + context.modalStack = modalStack.filter(item => item.id !== id); } } - - if (modalStack.length === 0) { - setTimeout(() => { - if (modalDom.parentNode) modalDom.parentNode.removeChild(modalDom); - - modalDom.style.display = 'none'; - this.modalDom = null; - }, 200); - } } }; diff --git a/packages/popup/index.vue b/packages/popup/index.vue index 7fe9dbf7a..3a39f2e27 100644 --- a/packages/popup/index.vue +++ b/packages/popup/index.vue @@ -1,6 +1,6 @@