前言
项目中,当需要用户处理事务,又不希望跳转页面以致打断工作流程时,我们会经常使用到对话框去承载相应的操作。虽然用得多,但是很多人其实并不知道怎么去写。饶有兴趣,自己尝试写了一个。
API
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
afterClose | Modal 完全关闭后的回调 | function | 无 |
cancelText | 取消按钮文字 | string | 取消 |
closable | 是否显示右上角的关闭按钮 | boolean | true |
destroyOnClose | 关闭时销毁 Modal | boolean | false |
footer | 底部内容,当不需要默认底部按钮时,可以设为 false | boolean | true |
maskClosable | 点击蒙层是否允许关闭 | boolean | true |
okText | 确认按钮文字 | string | 确定 |
title | 标题 | string|html节点 | 标题 |
width | 宽度 | number | 500 |
zIndex | 设置 Modal 的 z-index | number | 1000 |
keyboard | 是否支持键盘esc关闭 | boolean | true |
onCancel | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 |
onOk | 点击确定回调 | function(e) | 无 |
methods
参数 | 说明 | 使用形式 |
---|---|---|
set | 已覆盖默认的形式,设置新的属性,内部 Object.assign(this.options, newOptions) | model.set(newOptions) |
open | 打开对话框 | model.open() |
close | 关闭对话框 | model.close() |
destroy | 与close的区别在于,会从节点上删除 | model.destroy() |
代码
对话框全局中使用一个就够了,为了防止生成多个对话框,我们使用单例构造
var Dialog = (function () {
var instance;
return function (options) {
if (!instance) {
instance = new Modal(options);
}
return instance;
}
})()
这里主要运用到了闭包中的特性
modal.js代码
var Dialog = (function () {
var instance;
return function (options) {
if (!instance) {
instance = new Modal(options);
}
return instance;
}
})()
class Modal {
constructor(options) {
//默认属性
this.initOptions = {
title: '标题',
maskClosable: true,
header: true,
footer: true,
closable: true,
okText: '确 定',
cancelText: '取 消',
destroyOnClose: false,
keyboard: true,
zIndex: 1000,
width: 500,
afterClose: null
}
this.options = Object.assign({}, this.initOptions, options);
this.instance = document.createElement('div');//节点实例,因为只需要一个模态框,这里设置节点就可以了
this.mounted = false; //是否挂载在节点上
}
//处理模态框的点击事件
_modalClick(e) {
var className = e.target.className;
//匹配类名,例如 带有 class='modal-close' 的元素点击可以关闭模态框
if (new RegExp("(\\s|^)modal-close(\\s|$)").test(className)) {
this.cancel(); //关闭模态框
} else if (new RegExp("(\\s|^)modal-onOk(\\s|$)").test(className)) {
this.onOk(); //执行确定按钮的回调
} else if (new RegExp("(\\s|^)modal-container(\\s|$)").test(className)) {
this.outSideClick(); //模态框外的点击
}
}
//处理键盘ESC关闭
_escClose(e) {
var code = e.keyCode;
if (code === 27) {
this.cancel();
}
}
//渲染模态框节点
render() {
var modal = this.instance;
modal.style.zIndex = this.options.zIndex;
modal.className = 'modal-container';
var closeIcon = this.options.closable ? `<span class="modal-close">X</span>` : '';
var header = this.options.header ?
(this.options.header === true ?
`<div class='modal-header'>${this.options.title}
${closeIcon}
</div>` :
this.options.header) :
'';
var footer = this.options.footer ?
(this.options.footer === true ?
`<div class='modal-footer'>
<span class="modal-btn modal-close">${this.options.cancelText}</span>
<span class="modal-btn modal-onOk modal-btn-primary">${this.options.okText}</span>
</div>` :
this.options.footer) :
'';
modal.innerHTML = `<div class="modal" style="width: ${this.options.width}px">
${header}
<div class='modal-content'>${this.options.content}</div>
${footer}
</div>`;
}
//蒙层点击关闭
outSideClick() {
if (this.options.maskClosable) {
this.close();
}
}
//处理监听
listen() {
this._modalClick = this._modalClick.bind(this);
this.instance.addEventListener('click', this._modalClick);
if(this.options.keyboard){
this._escClose = this._escClose.bind(this);
window.addEventListener('keyup', this._escClose);
}
}
cancel(){
if(typeof this.options.onCancel === "function"){
this.options.onCancel();
}
this.close();
}
//点击确定回调
onOk() {
this
.options
.onOkFn();
this.close();
}
/****************** 提供的方法 ********************* */
//设置属性
set(options) {
Object.assign(this.options, options)
this.render()
}
//打开模态框
open() {
var modal = this.instance;
//实例如果没挂载
if (!this.mounted) {
this.mounted = true;
this.render();
document.body.appendChild(modal);
this.listen()
}
removeClass(modal, 'close');
addClass(modal, 'open');
}
//关闭模态框
close() {
var modal = this.instance
removeClass(modal, 'open');
addClass(modal, 'close');
if (this.options.destroyOnClose === true) {
this.destroy();
}
if (typeof this.options.afterClose === "function") {
var afterClose = this.options.afterClose.bind(this);
setTimeout(afterClose, 0);
}
}
//从节点上移除模态框
destroy() {
this.instance.removeEventListener('click', this._modalClick);//移除click监听
window.removeEventListener('keyup', this._escClose);//移除keyup监听
document.body.removeChild(this.instance);//移除模态框节点
this.mounted = false;
}
}
function hasClass(elements, cName) {
return !!elements
.className
.match(new RegExp("(\\s|^)" + cName + "(\\s|$)"));
};
function addClass(elements, cName) {
if (!hasClass(elements, cName)) {
elements.className += " " + cName;
};
};
function removeClass(elements, cName) {
if (hasClass(elements, cName)) {
elements.className = elements
.className
.replace(new RegExp("(\\s|^)" + cName + "(\\s|$)"), "");
};
};
modal.css
@keyframes openAnimate {
0% {
opacity: 0;
}
100%{
opacity: 1;
}
}
.open{
display: block !important;
animation: openAnimate .8s;
}
.close{
display: none;
}
.modal-container {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
background-color: #373737;
background-color: rgba(0,0,0,.65);
margin: auto;
filter: alpha(opacity=50);
text-align: center;
font-size: 0;
white-space: nowrap;
overflow: auto;
display: none;
}
.modal-container:after {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
}
.modal {
display: inline-block;
vertical-align: middle;
text-align: left;
font-size: 14px;
background-color: #fff;
border: 0;
border-radius: 4px;
background-clip: padding-box;
box-shadow: 0 4px 12px rgba(0,0,0,.15);
}
.modal-header{
padding: 16px 24px;
border-radius: 4px 4px 0 0;
background: #fff;
color: rgba(0,0,0,.65);
border-bottom: 1px solid #e8e8e8;
}
.modal-close{
float: right;
cursor: pointer;
}
.modal-content{
padding: 24px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
min-height: 200px;
}
.modal-footer {
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
border-radius: 0 0 4px 4px;
}
.modal-btn{
line-height: 32px;
display: inline-block;
font-weight: 400;
text-align: center;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
padding: 0 15px;
font-size: 14px;
border-radius: 4px;
height: 32px;
user-select: none;
transition: all .3s cubic-bezier(.645,.045,.355,1);
position: relative;
color: rgba(0,0,0,.65);
background-color: #fff;
border-color: #d9d9d9;
cursor: pointer;
}
.modal-btn-primary{
color: #fff;
background-color: #1890ff;
border-color: #1890ff;
}
如何使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="./modal.css">
</head>
<body>
<button class="open">1</button>
<button class="open">2</button>
<button class="open">3</button>
<script src="./modal.js"></script>
<script src="./index.js"></script>
</body>
</html>
index.js
var config = {
title: '模态框标题',
content: '',
header: true,
footer: true,
destroyOnClose: true,
onOkFn: function () {
alert('提交成功')
},
afterClose: function(){
alert('已经关闭')
}
}
var modal = new Dialog(config);
var openBtns = document.querySelectorAll('.open');
openBtns.forEach(function (item, index) {
item.onclick = function () {
var option = {
content: '这是第' + (index + 1) + '个'
}
modal.set(option)
modal.open();
}
})
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。