diff --git a/docs/markdown/en-US/popup.md b/docs/markdown/en-US/popup.md
index 5e14339c6..598bf31a5 100644
--- a/docs/markdown/en-US/popup.md
+++ b/docs/markdown/en-US/popup.md
@@ -43,6 +43,8 @@ Use `position` prop to set popup display position
 | overlay | Whether to show overlay | `Boolean` | `true` | - |
 | lockOnScroll | Lock body scroll | `Boolean` | `false` | - |
 | position | Position | `String` | - | `top` `bottom` `right` `left` |
+| overlayClass | Custom overlay class | `String` | `` | - |
+| overlayStyle | Custom overlay style | `Object` | `` | - |
 | closeOnClickOverlay | Close popup when click overlay | `Boolean` | `true` | - |
 | transition | Transition | `String` | `popup-slide` | - |
 | preventScroll | Prevent background scroll | `Boolean` | `false` | - |
diff --git a/docs/markdown/zh-CN/popup.md b/docs/markdown/zh-CN/popup.md
index 7492131b1..905082121 100644
--- a/docs/markdown/zh-CN/popup.md
+++ b/docs/markdown/zh-CN/popup.md
@@ -40,9 +40,11 @@ export default {
 | 参数 | 说明 | 类型 | 默认值 | 可选值 |
 |-----------|-----------|-----------|-------------|-------------|
 | v-model | 当前组件是否显示 | `Boolean` | `false` | - |
-| overlay | 是否显示背景遮罩层 | `Boolean` | `true` | - |
+| overlay | 是否显示背景蒙层 | `Boolean` | `true` | - |
 | lockOnScroll | 背景是否跟随滚动 | `Boolean` | `false` | - |
-| position | 弹出层位置 | `String` | - | `top` `bottom` `right` `left` |
-| closeOnClickOverlay | 点击遮罩层是否关闭弹出层 | `Boolean` | `true` | - |
-| transition | 弹出层的`transition` | `String` | `popup-slide` | - |
+| position | Popup 位置 | `String` | - | `top` `bottom` `right` `left` |
+| overlayClass | 自定义蒙层 class | `String` | `` | - |
+| overlayStyle | 自定义蒙层样式 | `Object` | `` | - |
+| closeOnClickOverlay | 点击蒙层是否关闭 Popup | `Boolean` | `true` | - |
+| transition | transition 名称 | `String` | `popup-slide` | - |
 | preventScroll | 是否防止滚动穿透 | `Boolean` | `false` | - |
diff --git a/docs/src/components/DemoList.vue b/docs/src/components/DemoList.vue
index 6ff02a794..153bd0a69 100644
--- a/docs/src/components/DemoList.vue
+++ b/docs/src/components/DemoList.vue
@@ -49,6 +49,8 @@ export default {
 
   methods: {
     switchLang(lang) {
+      const from = lang === 'zh-CN' ? 'en-US' : 'zh-CN';
+      this.$router.push(this.$route.path.replace(from, lang));
       setLang(lang);
     }
   }
diff --git a/packages/mixins/popup/Modal.vue b/packages/mixins/popup/Modal.vue
new file mode 100644
index 000000000..a9c663a73
--- /dev/null
+++ b/packages/mixins/popup/Modal.vue
@@ -0,0 +1,30 @@
+<template>
+  <div
+    class="van-modal"
+    :class="className"
+    :style="style"
+    @touchmove.prevent.stop
+    @click="$emit('click', $event)"
+  />
+</template>
+
+<script>
+export default {
+  name: 'van-modal',
+
+  props: {
+    zIndex: Number,
+    className: String,
+    customStyle: Object,
+  },
+
+  computed: {
+    style() {
+      return {
+        zIndex: this.zIndex,
+        ...this.customStyle
+      }
+    }
+  }
+};
+</script>
diff --git a/packages/mixins/popup/index.js b/packages/mixins/popup/index.js
index 38c69e677..522a93e86 100644
--- a/packages/mixins/popup/index.js
+++ b/packages/mixins/popup/index.js
@@ -112,9 +112,10 @@ export default {
           id: this._popupId,
           zIndex: context.plusKeyByOne('zIndex'),
           dom: this.$el,
-          extraClass: this.overlayClass,
-          extraStyle: this.overlayStyle
+          className: this.overlayClass,
+          customStyle: this.overlayStyle
         });
+
         if (this.lockOnScroll) {
           document.body.classList.add('van-overflow-hidden');
         }
diff --git a/packages/mixins/popup/popup-context.js b/packages/mixins/popup/popup-context.js
index 81035f451..14039e53c 100644
--- a/packages/mixins/popup/popup-context.js
+++ b/packages/mixins/popup/popup-context.js
@@ -2,14 +2,14 @@ const PopupContext = {
   idSeed: 1,
   zIndex: 2000,
   instances: {},
-  modalStack: [],
+  stack: [],
 
   plusKeyByOne(key) {
     return this[key]++;
   },
 
-  get topModal() {
-    return this.modalStack[this.modalStack.length - 1];
+  get top() {
+    return this.stack[this.stack.length - 1];
   }
 };
 
diff --git a/packages/mixins/popup/popup-manager.js b/packages/mixins/popup/popup-manager.js
index 4da186d7f..fc632526e 100644
--- a/packages/mixins/popup/popup-manager.js
+++ b/packages/mixins/popup/popup-manager.js
@@ -1,17 +1,22 @@
+import Vue from 'vue';
+import Modal from './Modal';
 import context from './popup-context';
 
+const modalDefaultConfig = {
+  className: '',
+  customStyle: {}
+};
+
 const PopupManager = {
   getModal() {
     let { modal } = context;
 
     if (!modal) {
-      modal = document.createElement('div');
-      modal.classList.add('van-modal');
-      modal.addEventListener('touchmove', event => {
-        event.preventDefault();
-        event.stopPropagation();
+      const ModalConstructor = Vue.extend(Modal);
+      modal = new ModalConstructor({
+        el: document.createElement('div')
       });
-      modal.addEventListener('click', () => {
+      modal.$on('click', () => {
         PopupManager.handleOverlayClick();
       });
 
@@ -23,52 +28,56 @@ const PopupManager = {
 
   // close popup when click modal && closeOnClickOverlay is true
   handleOverlayClick() {
-    const { topModal } = context;
-    if (topModal) {
-      const instance = context.instances[topModal.id];
+    const { top } = context;
+    if (top) {
+      const instance = context.instances[top.id];
       if (instance && instance.closeOnClickOverlay) {
         instance.close();
       }
     }
   },
 
-  openModal({ id, zIndex, dom, extraClass, extraStyle }) {
-    const { modalStack } = context;
-    const exist = modalStack.some(item => item.id === id);
+  openModal(config) {
+    const { id, dom } = config;
+    const exist = context.stack.some(item => item.id === id);
 
     if (!exist) {
-      const modal = this.getModal();
-      if (extraClass) {
-        modal.classList.add(extraClass);
-      }
-      if (extraStyle) {
-        modal.style.cssText = `${modal.style.cssText} ${extraStyle}`;
-      }
-      modal.style.zIndex = zIndex;
-
-      const parentNode = dom && dom.parentNode && dom.parentNode.nodeType !== 11 ? dom.parentNode : document.body;
-      parentNode.appendChild(modal);
-      modalStack.push({ id, zIndex, parentNode });
+      const targetNode = dom && dom.parentNode && dom.parentNode.nodeType !== 11 ? dom.parentNode : document.body;
+      context.stack.push({ id, config, targetNode });
+      this.updateModal();
     };
   },
 
   closeModal(id) {
-    const { modalStack } = context;
+    const { stack } = context;
 
-    if (modalStack.length) {
-      if (context.topModal.id === id) {
-        const modal = this.getModal();
-        modalStack.pop();
-        modal.parentNode.removeChild(modal);
-        if (modalStack.length) {
-          const { topModal } = context;
-          modal.style.zIndex = topModal.zIndex;
-          topModal.parentNode.appendChild(modal);
-        }
+    if (stack.length) {
+      if (context.top.id === id) {
+        stack.pop();
+        this.updateModal();
       } else {
-        context.modalStack = modalStack.filter(item => item.id !== id);
+        context.stack = stack.filter(item => item.id !== id);
       }
     }
+  },
+
+  updateModal() {
+    const modal = this.getModal();
+    const el = modal.$el;
+
+    if (el.parentNode) {
+      el.parentNode.removeChild(el);
+    }
+
+    if (context.top) {
+      const { targetNode, config } = context.top;
+
+      targetNode.appendChild(el);
+      Object.assign(modal, {
+        ...modalDefaultConfig,
+        ...config
+      });
+    }
   }
 };