最近在做一个基于zepto
的h5
项目,整个技术栈比较老旧,因此需要自己动手实现模态窗
。现在已经是react
、vue
这些mvvm
框架的天下了,请不要嘲笑我2018年还在使用zepto
开发,我也是很无奈呀~。不过无论这些轮子框架
发展到多么先进的地步,基础知识还是需要熟悉的嘛。本篇总结会介绍下模态窗
的概念以及如何实现。
什么是模态(modal)
不知道大家对模态
这个词怎么看,反正我最开始看到是一脸懵逼,不知道是什么意思,只是隐约觉得这种弹窗后面一般都有一个灰色蒙层。我是个喜欢追根溯源的人,一般碰到这种情况都会很纠结,非的弄明白为什么叫模态窗
。
后来查了下,发现模态窗
这个词是软件设计领域的词汇。咱们可以先看看普通的软件使用流程,在使用软件的时候,我们一般都会按照自己的思路以及软件内在的逻辑一步步操作,比如我们在使用一个购物系统,我们会按照我们对这个软件的固有理解来执行自己脑中的流程:选购商品,加入购物车,下单付款等等。这些流程可以说是我们使用软件时的一种正常操作状态
。
而模态
其实来源于单词modal
,表示一种特定模式的状态,那么是什么特定模式呢?其实是一种阻塞正常操作
的模式。模态窗
弹出时会使主窗口
变的不可用,只有在模态窗
内的操作完成后才能关闭模态窗
并返回正常主窗口
。关机模态窗
更加详细的介绍可以参考维基百科Modal window。
cssfixed
绝对定位
这里再介绍下css
中的绝对定位。为什么要介绍这个概念呢?因为我要实现的模态窗
需要保证水平&垂直
同时居中(大部分简单的模态窗
都是水平、垂直居中的),其实水平&垂直
同时居中的模态窗
的实现很简单,就是利用css
的fixed绝对定位
来实现的。
css
中position
包括absolute
、fixed
、relative
以及static
等定位方式,这里我们只介绍fixed
定位方式,其他的定位方式跟这篇总结的主题没关系,不过多讨论。
其实css
中fixed
定位是相对浏览器视口
,即view point
来定位。浏览器视口
其实就是浏览器抛掉了操作栏、边框后的窗口区域,如下图红色区域所示:
因此我们只需要将
背景蒙层
利用fixed
定位设置为完全覆盖浏览器视口
,模态窗
利用fixed
定位将弹窗进行居中定位即可:
.modal-layer {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
visibility: hidden;
transform: scale(1.1);
transition: opacity 0.25s 0s, transform 0.25s;
}
.modal-layer.show {
visibility: visible;
transform: scale(1.0);
transition: opacity 0.25s 0s, transform 0.25s;
}
.modal-dialog-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
border-radius: 0.5rem;
}
注意我们是通过show
这个className
来控制模态窗
以及背景蒙层
的展示与隐藏。
实现
由于我们是基于原生js来实现模态窗
,因此我们最好将弹窗封装称为一套工具函数:
- init,用来初始化
模态窗
; - destroy,用来在页面退出时销毁
模态窗
; - popup,弹出
模态窗
; - close,关闭
模态窗
。
function importFromBelow() {
/**
*
* @param {object} options init options
* @param {string} options.id dialog id
* @param {string} options.contentHtml dialog content html
* @param {boolean} options.closeWhenClickLayer whether close dialog when click layer
*/
function init(options) {
const {id: dialogId, contentHtml, closeWhenClickLayer = true} = options;
const layerEle = document.createElement('div');
layerEle.setAttribute('class', 'modal-layer');
layerEle.setAttribute('id', dialogId);
const contentContainerEle = document.createElement('div');
contentContainerEle.setAttribute('class', 'modal-dialog-container');
contentContainerEle.innerHTML = contentHtml;
layerEle.appendChild(contentContainerEle);
document.body.appendChild(layerEle);
if (closeWhenClickLayer) {
document.onclick = function (e) {
if (e.target === layerEle) {
close(dialogId);
}
}
}
}
/**
* destroy modal dialog
* @param {string} dialogId dialog id
*/
function destroy(dialogId) {
const dialogEle = document.querySelector(`#${dialogId}`);
dialogEle.parentNode.removeChild(dialogEle);
}
function popup(dialogId) {
const dialogEle = document.querySelector(`#${dialogId}`);
dialogEle.classList.add('show');
}
function close(dialogId) {
const dialogEle = document.querySelector(`#${dialogId}`);
dialogEle.classList.remove('show');
}
return {init, destroy, popup, close};
}
实现完成这套模态窗
工具函数后我们可以这样使用:
const dialogUtil = importFromBelow();
const DIALOG_ID = 'my-modal-dialog';
const contentHtml = `
<div class="modal-dialog-content" id="modal-dialog-content">
<div>这里是弹窗内容</div>
<div>
<button id="dialog-close-button">close modal dialog</button>
</div>
</div
`;
dialogUtil.init({
id: DIALOG_ID,
contentHtml,
// closeWhenClickLayer: false
});
const closeButtonEle = document.querySelector('#dialog-close-button');
closeButtonEle.onclick = function (e) {
dialogUtil.close(DIALOG_ID);
};
const triggerButton = document.querySelector('#trigger-button');
triggerButton.onclick = function (e) {
dialogUtil.popup(DIALOG_ID);
};
现在让我们看下效果:
完整的实现代码见这里。