前端新手项目练习之可拖动弹窗

前端新手项目练习之可拖动弹窗

前端新手记录自己在网络上找到的前端练习项目。最近工作忙得要死,感觉自己有些扛不住,这篇文章花了好久的时间。

项目简介

一个简单的弹窗,可以通过标题栏上下左右拖动,还可以通过四个边和四个角改变窗口的大小。另外,还可以通过右上角有最大化、最小化、复原、关闭的按钮来控制窗口。但是限制了窗口的最大和最小的宽度和高度。


可拖动窗口.gif

html部分

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>窗口拖曳</title>
        <link type="text/css" rel="stylesheet" href="popupBox.css">
    </head>
    <body>
        <div id="drag">
            <!--窗口标题栏-->
            <div class="title">
                <h2>可拖动的窗口</h2>
                <div>
                    <!--右上的窗口控制按钮,title是鼠标移上去显示的内容-->
                    <a class="min" href="javascript:;" title="最小化"></a>
                    <a class="max" href="javascript:;" title="最大化"></a>
                    <a class="revert" href="javascript:;" title="还原"></a>
                    <a class="close" href="javascript:;" title="关闭"></a>
                </div>
            </div>
            <!--控制窗口改变大小的元素-->
            <div class="resizeL"></div>
            <div class="resizeT"></div>
            <div class="resizeR"></div>
            <div class="resizeB"></div>
            <div class="resizeLT"></div>
            <div class="resizeTR"></div>
            <div class="resizeBR"></div>
            <div class="resizeLB"></div>
            <!--窗口内容-->
            <div class="content">
                1. 窗口可以拖动;<br />
                2. 窗口可以通过八个方向改变大小;<br />
                3. 窗口可以最小化、最大化、还原、关闭;<br />
                4. 限制窗口最小宽度/高度。
            </div>
        </div>
    </body>
    <script src="popupBox.js" type="text/javascript"></script>
</html>

整个弹窗分成两个部分,一部分是标题栏,上面有标题和4个按钮,一部分是内容栏,上面有一些窗口的介绍。另外还有控制窗口大小改变的8个元素。

CSS部分

body, div, h2 {
    margin: 0;
    padding: 0;
}
body {
    background-color: #333;
    font: 12px/1.5 \5fae\8f6f\96c5\9ed1; /* 12px字体,1.5倍行距,微软雅黑 */
}
#drag {
    position: absolute;
    top: 100px;
    left: 100px;
    width: 300px;
    height: 160px;
    background: #e9e9e9;
    border: 1px solid #444;
    border-radius: 5px;
    box-shadow: 0 1px 3px 2px #666;
}
#drag .title {
    position: relative;
    height: 27px;
    margin: 5px;
}
#drag .title h2 {
    font-size: 14px;
    height: 27px;
    line-height: 24px;
    border-bottom: 1px solid #A1B4B0;
}
#drag .title div {
    position: absolute;
    height: 19px;
    top: 2px;
    right: 0;
}
#drag .title a, a.open {
    float: left;
    width: 21px;
    height: 19px;
    display: block;
    margin-left: 5px;
    background:url(img/tool.png) no-repeat;
}
a.open{
    position: absolute;
    top: 10px;
    left: 50%;
    margin-left: -10px;
    background-position: 0 0;
}
a.open:hover {
    background-position: -29px -29px;
}
#drag .title a.min {
    background-position: -29px 0;
}
#drag .title a.min:hover{
    background-position: -29px -29px;
}
#drag .title a.max {
    background-position: -60px 0;
}
#drag .title a.max:hover{
    background-position: -60px -29px;
}
#drag .title a.revert {
    background-position: -149px 0;
}
#drag .title a.revert:hover{
    background-position: -149px -29px;
}
#drag .title a.close {
    background-position: -89px 0;
}
#drag .title a.close:hover{
    background-position: -89px -29px;
}
#drag .content {
    overflow: auto;
    margin: 0 5px;
}
#drag .resizeBR {
    position: absolute;
    width: 14px;
    height: 14px;
    right: 0;
    bottom: 0;
    overflow: hidden;
    cursor: nw-resize;
    background:url(img/resize.png) no-repeat;
}
#drag .resizeL, #drag .resizeT, #drag .resizeB, #drag .resizeR,
#drag .resizeLT,  #drag .resizeTR,  #drag .resizeLB {
    position: absolute;
    background: #000;
    overflow: hidden;
    opacity: 0; /* 透明度为0,不可见但可以触发事件 */
    filter: alpha(opacity=0); /* 透明度兼容IE */
}
#drag .resizeL, #drag .resizeR {
    top: 0;
    width: 5px;
    height: 100%;
    cursor: w-resize;
}
#drag .resizeR {
    right: 0;
}
#drag .resizeT, #drag .resizeB {
    width: 100%;
    height: 5px;
    cursor: n-resize;
}
#drag .resizeT {
    top: 0;
}
#drag .resizeB {
    bottom: 0;
}
#drag .resizeLT, #drag .resizeTR, #drag .resizeLB {
    width: 8px;
    height: 8px;
    background: #FF0;
}
#drag .resizeLT{
    top: 0;
    left: 0;
    cursor: nw-resize;
}
#drag .resizeTR{
    top:0;
    right:0;
    cursor:ne-resize;
}
#drag .resizeLB{
    left: 0;
    bottom: 0;
    cursor: ne-resize;
}

CSS部分,后半部分主要是将8个控制窗口大小的元素绝对定位到窗口的4个边和4个角,然后鼠标移上去会变成相应的形状。

javascript部分

本项目的javascript比较复杂,分着说明。

首先封装了个工具方法,用来根据id 、类名或标签获取元素内容。通过将常用的方法进行封装,一方面可以简化代码,方便开发,另一方面方便统一处理浏览器之间的兼容问题。

/**
 * 根据id 类名或标签获取元素的工具方法
 */
var get = {
    byId: function(id) {
        return typeof id === "string" ? document.getElementById(id) : id;
    },
    byClass: function(sClass, oParent) {
        //如果存在getElementsByClassName方法则使用,否则根据正则表达式搜索
        if(oParent.getElementsByClassName) {
            return oParent.getElementsByClassName(sClass);
        }
        else {
            var aClass = [];
            var reClass = new RegExp("(^| )" + sClass + "( |$)");
            var aElem = this.byTagName("*", oParent);
            for(var i = 0; i < aElem.length; i++) {
                reClass.test(aElem[i].className) && aClass.push(aElem[i]);
            }
            return aClass;
        }   
    },
    byTagName: function(elem, obj) {
        return (obj || document).getElementsByTagName(elem);
    }
};

//最小宽度和高度,用来还原窗口和保证内容的样式
var dragMinWidth = 250;
var dragMinHeight = 124;

下面定义处理拖动事件和4个按钮的函数。拖动事件主要是在鼠标按下的事件中添加鼠标移动的逻辑,根据鼠标移动的距离来决定窗口移动的距离。

/**
 * 用来处理拖动事件的函数
 * @method drag
 * @param {Element} oDrag 需要拖动的窗口元素
 * @param {Element} handle 触发拖动事件的元素
 */
function drag(oDrag, handle) {
    var disX = disY = 0;
    var oMin = get.byClass("min", oDrag)[0];//最小化按钮
    var oMax = get.byClass("max", oDrag)[0];//最大化按钮
    var oRevert = get.byClass("revert", oDrag)[0];//复原按钮
    var oClose = get.byClass("close", oDrag)[0];//关闭按钮

    handle = handle || oDrag;
    handle.style.cursor = "move";
    handle.onmousedown = function(event) {
        var event = event || window.event;//兼容IE,获取事件
        disX = event.clientX - oDrag.offsetLeft;
        disY = event.clientY - oDrag.offsetTop;

        document.onmousemove = function(event) {
            var event = event || window.event;
            var iL = event.clientX - disX;
            var iT = event.clientY - disY;
            //最大可拖动的范围
            var maxL = document.documentElement.clientWidth - oDrag.offsetWidth;
            var maxT = document.documentElement.clientHeight - oDrag.offsetHeight;

            if(iL <= 0) {
                iL = 0;
            }
            else if(iL >= maxL) {
                iL = maxL;
            }
            if(iT <= 0) {
                iT = 0;
            }
            else if(iT >= maxT) {
                iT = maxT;
            }

            oDrag.style.left = iL + "px";
            oDrag.style.top = iT + "px";

            return false;
        };

        //鼠标松开,清除事件
        document.onmouseup = function() {
            document.onmousemove = null;
            document.onmouseup = null;
            //释放鼠标捕获。IE才有
            if(this.releaseCapture)
                this.releaseCapture();
        };
        //设置鼠标捕获,IE才有
        if(this.setCapture) {
            this.setCapture();
        }
        return false;
    };
    //最大化按钮
    oMax.onclick = function() {
        oDrag.style.top = oDrag.style.left = 0;
        oDrag.style.width = document.documentElement.clientWidth - 2 + "px";
        oDrag.style.height = document.documentElement.clientWidth - 2 + "px";
        this.style.display = "none";
        oRevert.style.display = "block";
    };
    //还原按钮
    oRevert.onclick = function() {
        oDrag.style.width = dragMinWidth + "px";
        oDrag.style.height = dragMinHeight + "px";
        oDrag.style.left = (document.documentElement.clientWidth - oDrag.offsetWidth) / 2 + "px";
        oDrag.style.top = (document.documentElement.clientHeight - oDrag.offsetHeight) / 2 + "px";
        this.style.display = "none";
        oMax.style.display = "block";
    };
    //最小化按钮
    oMin.onclick = oClose.onclick = function() {
        oDrag.style.display = "none";
        var oA = document.createElement("a");
        oA.className = "open";
        oA.href = "javascript:;";
        oA.title = "还原";
        document.body.appendChild(oA);
        oA.onclick = function() {
            oDrag.style.display = "block";
            document.body.removeChild(this);
            this.onclick = null;
        };
    };
    //阻止冒泡
    oMin.onmousedown = oMax.onmousedown = oClose.onmousedown = function(event) {
        this.onfocus = function () {
            this.blur();//失去焦点
        };
        (event || window.event).cancelBubble = true;
    };
}

接下来定义通过鼠标来改变窗口大小的函数。该函数的逻辑比较复杂,既要考虑4个角4个边8种情况,还要考虑窗口大小变化过程中相对页面位置的改变。

/**
 * 改变窗口大小的函数
 * @method resize
 * @param {element} oParent 需要改变大小的窗口元素
 * @param {element} handle 用来触发鼠标事件的元素
 * @param {boolean} isLeft handle元素是否左侧元素
 * @param {boolean} isTop  handle元素是否顶部元素
 * @param {boolean} lockX  X轴宽度是否可以改变
 * @param {boolean} lockY  Y轴高度是否可以改变
 */
function resize(oParent, handle, isLeft, isTop, lockX, lockY) {
    //鼠标按下事件
    handle.onmousedown = function(event) {
        var event = event || window.event;
        var disX = event.clientX - handle.offsetLeft;//鼠标事件位置距离元素的x轴距离
        var disY = event.clientY - handle.offsetTop;//鼠标事件位置距离元素的y轴距离
        var iParentTop = oParent.offsetTop;//父元素(窗口)顶部位置
        var iParentLeft = oParent.offsetLeft;//父元素左侧位置
        var iParentWidth = oParent.offsetWidth;//父元素宽度
        var iParentHeight = oParent.offsetHeight;//父元素高度
        //鼠标移动事件
        document.onmousemove = function(event) {
            var event = event || window.event;

            var iL = event.clientX - disX;//x轴移动距离
            var iT = event.clientY - disY;//y轴移动距离
            var maxW = document.documentElement.clientWidth - oParent.offsetLeft - 2;//x轴最大可移动距离
            var maxH = document.documentElement.clientHeight - oParent.offsetTop - 2;//y轴最大可移动距离
            //调整大小后的元素宽度
            var iW = isLeft ? iParentWidth - iL : handle.offsetWidth + iL;
            //调整大小后的元素高度
            var iH = isTop ? iParentHeight - iT : handle.offsetHeight + iT;

            if(isLeft) { //窗口左侧定位发生变化
                oParent.style.left = iParentLeft + iL + "px";
            }
            if(isTop) { //窗口右侧定位发生变化
                oParent.style.top = iParentTop + iT + "px";
            }

            if(iW < dragMinWidth) {
                iW = dragMinWidth;
            } 
            else if(iW > maxW) {
                iW = maxW;
            }
            if(!lockX) {
                oParent.style.width = iW + "px";
            }

            if(iH < dragMinHeight) {
                iH = dragMinHeight;
            }
            else if(iH > maxH) {
                iH = maxH;
            }
            if(!lockY) {
                oParent.style.height = iH + "px";
            }

            //如果已经达到最小宽度或者高度,则清除事件
            if((isLeft && iW == dragMinWidth) || (isTop && iH == dragMinHeight)) {
                document.onmousemove = null;
            }

            return false;
        }

        document.onmouseup = function() {
            document.onmousemove = null;
            document.onmouseup = null;
        };

        return false;
    }
};

最后就是加载页面时候的函数。

window.onload = window.onresize = function() {
    //获取元素
    var oDrag = document.getElementById("drag");
    var oTitle = get.byClass("title", oDrag)[0];
    var oL = get.byClass("resizeL", oDrag)[0];
    var oT = get.byClass("resizeT", oDrag)[0];
    var oR = get.byClass("resizeR", oDrag)[0];
    var oB = get.byClass("resizeB", oDrag)[0];
    var oLT = get.byClass("resizeLT", oDrag)[0];
    var oTR = get.byClass("resizeTR", oDrag)[0];
    var oBR = get.byClass("resizeBR", oDrag)[0];
    var oLB = get.byClass("resizeLB", oDrag)[0]; 
    
    drag(oDrag, oTitle);
    //根据四角改变窗口大小
    resize(oDrag, oLT, true, true, false, false);
    resize(oDrag, oTR, false, true, false, false);
    resize(oDrag, oBR, false, false, false, false);
    resize(oDrag, oLB, true, false, false, false);
    //根据四边改变窗口大小
    resize(oDrag, oL, true, false, false, true);
    resize(oDrag, oT, false, true, true, false);
    resize(oDrag, oR, false, false, false, true);
    resize(oDrag, oB, false, false, true, false);
    //定位窗口的左侧和顶部,用来保证窗口居中
    oDrag.style.left = (document.documentElement.clientWidth - oDrag.offsetWidth) / 2 + "px";
    oDrag.style.top = (document.documentElement.clientHeight - oDrag.offsetHeight) / 2 + "px";
}

总结

本来以为像弹窗这样经常使用的组件写起来应该很简单,写完这个项目才发现并没有想象中那么容易,其中也涉及到很多逻辑和知识,感觉自己前端要学习的东西还很多。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355