前言

项目中,当需要用户处理事务,又不希望跳转页面以致打断工作流程时,我们会经常使用到对话框去承载相应的操作。虽然用得多,但是很多人其实并不知道怎么去写。饶有兴趣,自己尝试写了一个。

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();
    }

})

图片描述


Ryan
91 声望3 粉丝