自定义web弹窗/层:简易风格的msg与可拖放的dialog

  前言

  做过web项目开发的人对layer弹层组件肯定不陌生,作为layUI的一个重要组件,使用简单、接口参数丰富,功能健壮,深受广大开发者的喜爱,作为一个热(经)爱(常)工(划)作(水),喜欢钻研探索技术的程序员(狗),我们自己来实现一个web弹窗/层,一窥layer的本源(/手动滑稽脸),进步,从模仿开始。

  首先,模仿layer的部分功能,我们先确定下我们要实现的功能:

  msg

  1、居中弹出、延时销毁、自适应宽高、支持参数配置

  dialog

  1、可拖动

  2、可最小化、最大化、关闭

  3、右下角可对窗口进行缩放

  4、支持多个参数配置、扩展

 

  代码编写

  大部分的思路都在代码注释里

  css样式

        /* web弹窗 */
        .tip-msg {
            background-color: rgba(61, 61, 61, 0.93);
            color: #ffffff;
            opacity: 0;
            max-width: 200px;
            position: absolute;
            text-align: center;
            line-height: 25px;
            border-radius: 30px;
            padding: 5px 15px;
            display: inline-block;
        }

        .tip-shade {
            z-index: 9999;
            background-color: rgb(0, 0, 0);
            opacity: 0.6;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }

        .tip-dialog {
            z-index: 10000;
            position: absolute;
            display: block;
            background: #e9e9e9;
            border-radius: 5px;
            opacity: 0;
            border: 1px solid #dad8d8;
            box-shadow: 0px 1px 20px 2px rgb(255, 221, 221);
        }

        .tip-title {
            cursor: move;
            padding: 5px;
            position: relative;
            height: 25px;
            border-bottom: 1px solid #dad8d8;
            user-select: none;
        }

        .tip-title-text {
            margin: 0;
            padding: 0;
            font-size: 15px;
        }

        .tip-title-btn {
            position: absolute;
            top: 5px;
            right: 5px;
        }

        .tip-content {
            padding: 8px;
            position: relative;
            word-break: break-all;
            font-size: 14px;
            overflow-x: hidden;
            overflow-y: auto;
        }

        .tip-resize {
            position: absolute;
            width: 15px;
            height: 15px;
            right: 0;
            bottom: 0;
            cursor: se-resize;
        }

  js

    /**
     * 自定义web弹窗/层:简易风格的msg与可拖放的dialog
     * 依赖jquery
     */
    var tip = {

        /**
         * 初始化
         */
        init: function () {
            var titleDiv = null;//标题元素
            var dialogDiv = null;//窗口元素
            var titleDown = false;//是否在标题元素按下鼠标
            var resizeDown = false;//是否在缩放元素按下鼠标
            var offset = {x: 0, y: 0};//鼠标按下时的坐标系/计算后的坐标
            /*
                使用 on() 方法添加的事件处理程序适用于当前及未来的元素(比如由脚本创建的新元素)。
                问题:事件绑定在div上出现div移动速度跟不上鼠标速度,导致鼠标移动太快时会脱离div,从而无法触发事件。
                解决:把事件绑定在document文档上,无论鼠标在怎么移动,始终是在文档范围之内。
            */
            //鼠标在标题元素按下
            $(document).on("mousedown", ".tip-title", function (e) {
                var event1 = e || window.event;
                titleDiv = $(this);
                dialogDiv = titleDiv.parent();
                titleDown = true;
                offset.x = e.clientX - parseFloat(dialogDiv.css("left"));
                offset.y = e.clientY - parseFloat(dialogDiv.css("top"));
            });
            //鼠标移动
            $(document).on("mousemove", function (e) {
                var event2 = e || window.event;
                var eveX = event2.clientX;             // 获取鼠标相对于浏览器x轴的位置
                var eveY = event2.clientY;             // 获取鼠标相对于浏览器Y轴的位置
                // var height = document.body.clientHeight;//表示HTML文档所在窗口的当前高度;
                // var width = document.body.clientWidth;//表示HTML文档所在窗口的当前宽度;
                var height = window.innerHeight;//浏览器窗口的内部高度;
                var width = window.innerWidth;//浏览器窗口的内部宽度;

                //在标题元素按下
                if (titleDown) {

                    //处理滚动条
                    if (tip.hasXScrollbar()) {
                        height = height - tip.getScrollbarWidth();
                    }
                    if (tip.hasYScrollbar()) {
                        width = width - tip.getScrollbarWidth();
                    }

                    //上边
                    var top = (eveY - offset.y);
                    if (top <= 0) {
                        top = 0;
                    }
                    if (top >= (height - dialogDiv.height())) {
                        top = height - dialogDiv.height() - 5;
                    }

                    //左边
                    var left = (eveX - offset.x);
                    if (left <= 0) {
                        left = 0;
                    }
                    if (left >= (width - dialogDiv.width())) {
                        left = width - dialogDiv.width() - 5;
                    }
                    dialogDiv.css({
                        "top": top + "px",
                        "left": left + "px"
                    });
                }

                //在缩放元素按下
                if (resizeDown) {
                    var newWidth = (dialogDiv.resize.width + (eveX - offset.x));
                    if (dialogDiv.resize.initWidth >= newWidth) {
                        newWidth = dialogDiv.resize.initWidth;
                    }
                    var newHeight = (dialogDiv.resize.height + (eveY - offset.y));
                    if (dialogDiv.resize.initHeight >= newHeight) {
                        newHeight = dialogDiv.resize.initHeight;
                    }

                    dialogDiv.css("width", newWidth + "px");
                    dialogDiv.find(".tip-content").css("height", newHeight + "px");
                }
            });
            //鼠标弹起
            $(document).on("mouseup", function (e) {
                //清空对象
                titleDown = false;
                resizeDown = false;
                titleDiv = null;
                dialogDiv = null;
                offset = {x: 0, y: 0};
            });
            //阻止按钮事件冒泡
            $(document).on("mousedown", ".tip-title-min,.tip-title-max,.tip-title-close", function (e) {
                e.stopPropagation();//阻止事件冒泡
            });
            //最小化
            $(document).on("click", ".tip-title-min", function (e) {
                // var height = document.body.clientHeight;//表示HTML文档所在窗口的当前高度;
                // var width = document.body.clientWidth;//表示HTML文档所在窗口的当前宽度;
                var height = window.innerHeight;//浏览器窗口的内部高度;
                var width = window.innerWidth;//浏览器窗口的内部宽度;
                var $parent = $(this).parents(".tip-dialog");
                //当前是否为最大化
                if ($parent[0].isMax) {
                    $parent[0].isMax = false;
                    $parent.css({
                        "top": $parent[0].topMin,
                        "left": $parent[0].leftMin,
                        "height": $parent[0].heightMin,
                        "width": $parent[0].widthMin
                    });
                }
                //当前是否为最小化
                if (!$parent[0].isMin) {
                    $parent[0].isMin = true;
                    $parent[0].bottomMin = $parent.css("bottom");
                    $parent[0].leftMin = $parent.css("left");
                    $parent[0].heightMin = $parent.css("height");
                    $parent[0].widthMin = $parent.css("width");
                    $parent.css({
                        "top": "",
                        "bottom": "5px",
                        "left": 0,
                        "height": "30px",
                        "width": "95px"
                    });
                    $parent.find(".tip-title-text").css("display", "none");
                    $parent.find(".tip-content").css("display", "none");
                } else {
                    $parent[0].isMin = false;
                    $parent.css({
                        "top": $parent[0].topMin,
                        "bottom": $parent[0].bottomMin,
                        "left": $parent[0].leftMin,
                        "height": $parent[0].heightMin,
                        "width": $parent[0].widthMin
                    });
                    $parent.find(".tip-title-text").css("display", "block");
                    $parent.find(".tip-content").css("display", "block");
                }
            });
            //最大化
            $(document).on("click", ".tip-title-max", function (e) {
                // var height = document.body.clientHeight;//表示HTML文档所在窗口的当前高度;
                // var width = document.body.clientWidth;//表示HTML文档所在窗口的当前宽度;
                var height = window.innerHeight;//浏览器窗口的内部高度;
                var width = window.innerWidth;//浏览器窗口的内部宽度;
                var $parent = $(this).parents(".tip-dialog");
                //当前是否为最小化
                if ($parent[0].isMin) {
                    $parent[0].isMin = false;
                    $parent.css({
                        "top": $parent[0].topMin,
                        "bottom": $parent[0].bottomMin,
                        "left": $parent[0].leftMin,
                        "height": $parent[0].heightMin,
                        "width": $parent[0].widthMin
                    });
                    $parent.find(".tip-title h2").css("display", "block");
                }
                //当前是否为最大化
                if (!$parent[0].isMax) {
                    $parent[0].isMax = true;
                    $parent[0].topMin = $parent.css("top");
                    $parent[0].leftMin = $parent.css("left");
                    $parent[0].heightMin = $parent.css("height");
                    $parent[0].widthMin = $parent.css("width");
                    $parent.css({
                        "top": 0,
                        "left": 0,
                        "height": height - 5 + "px",
                        "width": width - 5 + "px"
                    });
                } else {
                    $parent[0].isMax = false;
                    $parent.css({
                        "top": $parent[0].topMin,
                        "left": $parent[0].leftMin,
                        "height": $parent[0].heightMin,
                        "width": $parent[0].widthMin
                    });
                }
            });
            //缩放
            $(document).on("mousedown", ".tip-resize", function (e) {
                var event1 = e || window.event;
                dialogDiv = $(this).parent();
                resizeDown = true;
                offset.x = e.clientX;
                offset.y = e.clientY;
                //点击时的宽高
                dialogDiv.resize.width = dialogDiv.width();
                dialogDiv.resize.height = dialogDiv.find(".tip-content").height();
            });
            //关闭
            $(document).on("click", ".tip-title-close", function (e) {
                $(this).parents(".tip-dialog").parent().remove();
            });
        },

        /**
         * 是否存在X轴方向滚动条
         */
        hasXScrollbar: function () {
            return document.body.scrollWidth > (window.innerWidth || document.documentElement.clientWidth);
        },

        /**
         * 是否存在Y轴方向滚动条
         */
        hasYScrollbar: function () {
            return document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight);
        },

        /**
         * 计算滚动条的宽度
         */
        getScrollbarWidth: function () {
            /*
                思路:生成一个带滚动条的div,分析得到滚动条长度,然后过河拆桥
             */
            var scrollDiv = document.createElement("div");
            scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
            document.body.appendChild(scrollDiv);
            var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
            document.body.removeChild(scrollDiv);

            return scrollbarWidth;

        },

        /**
         * tip提示
         * tip.msg("哈哈哈哈哈");
         * tip.msg({text:"哈哈哈哈哈",time:5000});
         */
        msg: function (setting) {
            var time = setting.time || 2000; // 显示时间(毫秒) 默认延迟2秒关闭
            var text = setting.text || setting; // 文本内容

            //组装HTML
            var tip = "<div class='tip tip-msg'>"
                + text +
                "</div>";

            //删除旧tip
            $(".tip-msg").remove();

            //添加到body
            $("body").append(tip);

            //获取jq对象
            var $tip = $(".tip-msg");

            //动画过渡
            $tip.animate({opacity: 1}, 500);

            //计算位置浏览器窗口上下、左右居中
            // var height = document.body.clientHeight;//表示HTML文档所在窗口的当前高度;
            var width = document.body.clientWidth;//表示HTML文档所在窗口的当前宽度;
            var height = window.innerHeight;//浏览器窗口的内部高度;
            // var width = window.innerWidth;//浏览器窗口的内部宽度;
            width = ((width / 2) - ($tip.css("width").replace("px", "") / 2)) / width;
            height = ((height / 2) - ($tip.css("height").replace("px", "") / 2)) / height;
            $tip.css({
                "top": (height * 100) + "%",
                "left": (width * 100) + "%"
            });

            //延迟删除
            setTimeout(function () {
                //动画过渡
                $tip.animate({opacity: 0}, 500, function () {
                    $tip.remove();
                });
            }, time);
        },

        /**
         * 可拖放窗口
         * tip.dialog({title:"测试弹窗标题",content:"测试弹窗内容"});
         * tip.dialog({title:"测试弹窗标题",content:"<h1>测试弹窗内容</h1>",offset: ['100px', '50px'],area:["200px","100px"],shade:0});
         */
        dialog: function (setting) {
            var title = setting.title || "这里是标题"; // 标题
            var content = setting.content || "这里是内容"; // 内容
            var area = setting.area || ['393px', '222px']; // 宽高
            var offset = setting.offset || "auto" // 位置 上、左
            var shade = setting.shade != undefined ? setting.shade : 0.7;//遮阴 为0时无遮阴对象

            //组装HTML
            var tip = "<div>\n" +
                "    <!-- 遮阴层 -->\n" +
                "    <div class=\"tip tip-shade\"></div>\n" +
                "    <!-- 主体 -->\n" +
                "    <div class=\"tip tip-dialog\">\n" +
                "        <!-- 标题 -->\n" +
                "        <div class=\"tip tip-title\">\n" +
                "            <h2 class=\"tip tip-title-text\"></h2>\n" +
                "            <div class=\"tip tip-title-btn\">\n" +
                "                <button class=\"tip tip-title-min\" title=\"最小化\">--</button>\n" +
                "                <button class=\"tip tip-title-max\" title=\"最大化\">O</button>\n" +
                "                <button class=\"tip tip-title-close\" title=\"关闭\">X</button>\n" +
                "            </div>\n" +
                "        </div>\n" +
                "        <!-- 窗口内容 -->\n" +
                "        <div class=\"tip tip-content\"></div>\n" +
                "        <!-- 右下角改变窗口大小 -->\n" +
                "        <div class=\"tip tip-resize\"></div>\n" +
                "    </div>\n" +
                "</div>";

            var $tip = $(tip);

            //添加到body
            $("body").append($tip)

            //设置遮阴
            $tip.find(".tip-shade").css("opacity", shade);
            if(shade == 0){
                $tip.find(".tip-shade").css({
                    "width":"0",
                    "height":"0"
                });
            }

            //获取dialog对象
            $tip = $tip.find(".tip-dialog");

            //标题
            $tip.find(".tip-title-text").text(title);

            //内容
            $tip.find(".tip-content").append(content);

            //设置初始宽高
            $tip.css({
                "width": area[0],
            });
            $tip.find(".tip-content").css({
                "height": area[1]
            });

            //动画过渡
            $tip.animate({opacity: 1}, 500);

            //计算位置浏览器窗口上下、左右居中
            if (offset === "auto") {
                // var height = document.body.clientHeight;//表示HTML文档所在窗口的当前高度;
                var width = document.body.clientWidth;//表示HTML文档所在窗口的当前宽度;
                var height = window.innerHeight;//浏览器窗口的内部高度;
                // var width = window.innerWidth;//浏览器窗口的内部宽度;
                width = ((width / 2) - ($tip.css("width").replace("px", "") / 2)) / width;
                height = ((height / 2) - ($tip.css("height").replace("px", "") / 2)) / height;
                $tip.css({
                    "top": (height * 100) + "%",
                    "left": (width * 100) + "%"
                });
            } else if (Array.isArray(offset)) {
                $tip.css({
                    "top": offset[0],
                    "left": offset[1]
                });
            }

            //初始值宽高
            $tip.resize.initWidth = $tip.width();
            $tip.resize.initHeight = $tip.find(".tip-content").height();
        }
    };

  调用

    //初始化
    tip.init();

    // tip提示
    tip.msg("哈哈哈哈哈");
    tip.msg({text:"哈哈哈哈哈",time:5000});

    //可拖放窗口
    tip.dialog({title:"测试弹窗标题",content:"测试弹窗内容"});
    tip.dialog({title:"测试弹窗标题",content:"<h1>测试弹窗内容</h1>",offset: ['100px', '50px'],area:["200px","100px"],shade:0});

 

  效果演示

  msg

  弹出、销毁有动画效果,设置了一个max-width,超出会换行,支持参数配置

  

  dialog

  可拖动、可最小化、最大化、关闭,右下角可进行缩放,支持参数配置,发光特效

 

  总结

  实现拖拽、缩放功能,主要是监听了document的mousedown,mousemove,mouseup事件,并对特殊元素进行特殊处理,一波骚操作,玩出了花(滑稽),从而实现我们想要的效果。

  参考:

  http://layer.layui.com/

  http://www.17sucai.com/pins/demo-show?id=4218

  https://www.cnblogs.com/nzbin/p/8117535.html