页面拖拽元素原理及实现代码

今天总结下常见的拖拽效果的实现原理及场景应用,拖拽功能主要是用在让用户做一些自定义的动作,比如拖动排序,弹出框拖动移动等等,效果还是蛮不错的

 

现在有多种实现方法,常用的有H5的拖放API和原生JS两种方法。

(1)HTML5提供专门的拖拽与拖放的API,在不考虑兼容的情况下,以后实现这类效果就不必乱折腾了

【兼容:IE9+和其他主流浏览器均支持,而IE8和早前版本均不支持draggable属性】

(2)原生JS实现

接下来我分开解释下

 

(1)H5的draggable属性实现

首先说下HTML5里新增的draggable属性

在html5中,默认<img.../>和设置了href的<a>是可拖动的,其他都需手动把元素的draggable属性设置为true

然后使元素携带数据应该为被拖动元素的ondragstart拖拽开始(开始拖动触发该事件)事件指定监听器,在监听器中让拖动操作可以携带数据。

之后为了让document接受放的动作,为documen的ondragover(拖拽结束)事件设定监听器,在监听器中取消document对拖动事件的默认行为。

然而,不同浏览器在元素拖到指定位置释放之后默认的动作是不同的(firefox释放后会跳转到新页面,chrome则没有任何动作),所以我们要取消拖放操作的默认动作,为document的ondrop绑定监听器

 

【简答通俗讲】就是:

H5允许对页面元素进行拖动,只要在元素的属性中加上draggable="true",就可以拖动了。

在拖动的同时,必须记录被拖动的元素,通过在元素上注册事件ondragstart即可实现,比如规定了一个img元素可以拖动,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>元素拖拽</title>
</head>
<body>
<img src="./01.jpg" draggble="true" id="picture" ondragstart="drag(event);">
<script>
    function drag(event) {
        event.dataTransfer.setData("Text",event.target.id);
        console.log(event.target.id)
    }
</script>
</body>
</html>

在拖动img的时候,drag函数就会把img的id属性记录在事件中,记录值的类型是Text。

【下一步】是规定哪些元素可以放置被拖动的img,比如一个div可以放置这个被拖动的img, 需要在这个div上定义一个事件:ondragover(被拖动元素进入本元素的范围内拖动时不断触发)。在这个事件中,需要阻止对事件的默认处理方式,其实就是硬性的加上一句:event.preventDefault()

<style type="text/css">
    .main{
        width: 200px;
        height: 300px;
        border: 1px solid red;
    }
</style>
<div class="main" ondragover="allowDrop(event);"></div>
    function allowDrop(event) {
        alert("进入范围")
        event.preventDefault();
    }

 这里通过运用ondragover触发事件,我们只要将图片拖拽到框里,便会提示已经入范围框

【下一步】要在容纳这个img的div中把这个img放到里面,当放置的时候,会触发ondrop事件(其他元素被放到了本元素时触发)。

(1)通过event.dataTransfer.getData("Text")来得到上面event.dataTransfer.setData("Text",ev.target.id)的img的id

(2)通过event.target.appendChild(document.getElementById(data))来把img放到div中,注意这里event.target指的是div,不是上面的img了。至此,页面的拖动完成了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>元素拖拽</title>
    <style type="text/css">
        .main{
            width: 300px;
            height: 300px;
            border: 1px solid red;
            position: absolute;
            top: 300px;
            left: 300px;
        }
    </style>
</head>
<body>
<img src="./01.jpg" draggble="true" id="picture" ondragstart="drag(event);">
<div class="main" ondragover="allowDrop(event);" ondrop="drop(event)"></div>
<script>
    function drag(event) {
        event.dataTransfer.setData("Text",event.target.id);
        console.log(event.target.id)
    }
    function allowDrop(event) {
        console.log("进入范围")
        event.preventDefault();
    }
    function drop(event) {
        console.log("鼠标已松开")
        event.preventDefault();
        var data=event.dataTransfer.getData("Text");
        console.log(event.target)
        event.target.appendChild(document.getElementById(data));
    }
</script>
</body>
</html>

 

【W3实例讲解:】

 

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>拖放Demo</title>
    <style type="text/css">
        #div1 {
            width: 198px;
            height: 66px;
            padding: 10px;
            border: 1px solid #aaaaaa;
        }
    </style>
    <script type="text/javascript">
        function allowDrop(ev) {
            ev.preventDefault();
        }
        function drag(ev) {
            ev.dataTransfer.setData("Text", ev.target.id);
        }
        function drop(ev) {
            ev.preventDefault();
            var data = ev.dataTransfer.getData("Text");
            ev.target.appendChild(document.getElementById(data));
        }
    </script>
</head>
<body>
<p>请把 W3School 的图片拖放到矩形中:</p>
<div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<br />
<img id="drag1" src="http://www.w3school.com.cn/i/eg_dragdrop_w3school.gif" draggable="true" ondragstart="drag(event)" />
</body>
</html>
 下面研究一下拖放事件的所有不同部分

 ①把元素设置为可拖放

首先:为了把一个元素设置为可拖放,请把 draggable 属性设置为 true:

<img draggable="true">
 ②拖放的内容 - ondragstart 和 setData()

然后,规定当元素被拖动时发生的事情。在上面的例子中,ondragstart 属性调用了一个 drag(event) 函数,规定拖动什么数据。

dataTransfer.setData() 方法设置被拖动数据的数据类型和值:

function drag(ev) {
    ev.dataTransfer.setData("text", ev.target.id);
}

在本例中,数据类型是 "text",而值是这个可拖动元素的 id ("drag1")

③拖到何处 - ondragover

ondragover 事件规定被拖动的数据能够被放置到何处

默认地,数据/元素无法被放置到其他元素中。为了实现拖放,我们必须阻止元素的这种默认的处理方式。

这个任务由 ondragover 事件的 event.preventDefault() 方法完成:

event.preventDefault()
 ④进行放置 - ondrop

当放开被拖数据时,会发生 drop 事件。

在上面的例子中,ondrop 属性调用了一个函数,drop(event):

function drop(ev) {
    ev.preventDefault();
    var data = ev.dataTransfer.getData("text");
    ev.target.appendChild(document.getElementById(data));
}

【代码解释:】

 1. 调用 preventDefault() 来阻止数据的浏览器默认处理方式(drop 事件的默认行为是以链接形式打开)

 2 .通过 dataTransfer.getData() 方法获得被拖的数据,将返回在 setData() 方法中设置为相同类型的任何数据

 3 .被拖数据是被拖元素的 id ("drag1")

 4 .把被拖元素追加到放置元素中

【更多实例】

来回拖放图片:在两个 <div> 元素之间来回拖放图像

 
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>拖放Demo</title>
    <style type="text/css">
        #div1, #div2 {
            float: left;
            width: 198px;
            height: 66px;
            margin: 10px;
            padding: 10px;
            border: 1px solid #aaaaaa;
        }
    </style>
    <script type="text/javascript">
        function allowDrop(ev) {
            ev.preventDefault();
        }

        function drag(ev) {
            ev.dataTransfer.setData("Text", ev.target.id);
        }

        function drop(ev) {
            ev.preventDefault();
            var data = ev.dataTransfer.getData("Text");
            ev.target.appendChild(document.getElementById(data));
        }
    </script>
</head>
<body>
<div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)">
    <img src="http://www.w3school.com.cn/i/eg_dragdrop_w3school.gif" draggable="true" ondragstart="drag(event)"
         id="drag1"/>
</div>
<div id="div2" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
</body>
</html>

 

 

这里我再列个W3的例子:

<!DOCTYPE html>
<html class="no-js">

    <head>
        <meta charset="utf-8">
        <title>HTML5-draggable(拖放)</title>
        <style type="text/css">
            #div1, #div2 {float:left; width:100px; height:35px; 
             margin:10px;padding:10px;border:1px solid #aaaaaa;}
        </style>
        <script src="js/modernizr.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            /*
             * 虽然已经设定了img元素可被拖动,但是浏览器默认地,
             无法将数据/元素放置到其他元素中。
             * 如果有需要设置某些元素可接受被拖动元素,则要阻止它的默认行为,
             * 这要通过设置该接收元素的ondragover 事件,调用event.preventDefault() 方法
             */
            function allowDrop(ev) {
                ev.preventDefault(); //阻止默认行为
                
                //ev.target.id
                //此处ev.target是接收元素,通过事件被绑定在哪个元素即可区分
            }

            /*
             * 当该img元素被拖动时,会触发一个ondragstart 事件,
               该事件调用了一个方法drag(event)。
             */
            function drag(ev) {
                //ev.dataTransfer.setData() 方法设置被拖数据的数据类型(Text)和
                //值(被拖元素id),
                //该方法将被拖动元素的id存储到事件的dataTransfer对象内,
                //ev.dataTransfer.getData()可将该元素取出。
                //此处ev.target是被拖动元素
                ev.dataTransfer.setData("Text", ev.target.id); 
            }

            /*
             * 当被拖元素移动到接收元素,
             * 松开鼠标时(即被拖元素放置在接收元素内时)会出发ondrop事件
             */
            function drop(ev) {
                ev.preventDefault(); //阻止默认行为
                var data = ev.dataTransfer.getData("Text"); //将被拖动元素id取出
                ev.target.appendChild(document.getElementById(data)); 
                //将被拖动元素添加到接收元素尾部
            }
        </script>
    </head>

    <body>

        <div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)">
            <!--为了使元素可拖动,把 draggable 属性设置为 true--> 
            <img src="http://www.w3school.com.cn/i/w3school_logo_black.gif" 
                 draggable="true" ondragstart="drag(event)" id="drag1" />
        </div>
        
        <div id="div2" ondrop="drop(event)" ondragover="allowDrop(event)"></div>

    </body>

</html>

  

 

接下来举个简单随意拖放例子:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8"/>
    <title>可拖动</title>
    <style type="text/css">
        #source{
            border: 1px solid #000000;
            background-color: #ccc;
            width: 80px;
            height: 80px;
        }
    </style>

</head>
<body>
<div id="source">
    HELLO WORLD。
</div>
<script type="text/javascript">
    var source = document.getElementById("source");
    source.ondragstart = function(evt){//ondragstart:开始拖动时触发
        //让拖动携带数据
        console.log(evt)
        evt.dataTransfer.setData("text/plain","www.fkjava.org");
    }
    document.ondragover = function(evt){
     //ondragover:被拖动元素进入本元素的范围内拖动时不断触发
        //取消事件的默认行为
        return false;
    }
    document.ondrop = function(evt){//ondrop:其他元素被放到了本元素时触发
        source.style.position = "absolute";
        source.style.left = evt.pageX + "px";
        source.style.top = evt.pageY + "px";
        //取消事件的默认行为
        return false;
    }
</script>
</body>
</html>

附:触发事件

ondragstart:开始拖动时触发

ondrag:拖动过程中不断触发

ondragend:拖动结束时触发

ondragenter:被拖动元素进入本元素的范围内时触发

ondragover:被拖动元素进入本元素的范围内拖动时不断触发

ondragleave:被拖动元素离开本元素时触发

ondrop:其他元素被放到了本元素时触发

 

【再来说下DataTransfer对象】

拖放触发的拖放事件有一个dataTransfer的属性,该属性值是一个DataTransfer对象(可以去chrome浏览器

控制台打印验证,这里我已经打印出来,打开控制台找打即可)

dataTransfer对象:提供了对于预定义的剪贴板格式的访问,以便在拖曳操作中使用。

通俗的讲就是在拖曳操作的过程中,我们可以用过dataTransfer对象来传输数据,以便在拖曳操作结束的时候对数据进行其他的操作。

 

【对象属性】

(1)dropEffect:设置或返回拖放目标上允许发生的拖放行为。如果此处设置的拖放行为不再effectAllowed属性设置的多种拖放行为之内,拖放操作将会失败。该属性值只允许为“null”、“copy”、“link”和“move”四值之一。

(2)effectAllowed:设置或返回被拖动元素允许发生的拖动行为。该属性值可设为“none”、“copy”、“copyLink”、“copyMove”、“link”、“linkMove”、“move”、“all”和“uninitialized”。

(3)items:该属性返回DataTransferItems对象,该对象代表了拖动数据。

(4)types:该属性返回一个DOMStringList对象,该对象包括了存入dataTransfer中数据的所有类型。

 

【对象方法】

(1)setData(format,data):将指定格式的数据赋值给dataTransfer对象,参数format定义数据的格式也就是数据的类型,data为待赋值的数据

(2)getData(format):从dataTransfer对象中获取指定格式的数据,format代表数据格式,data为数据。

(3)clearData([format]):从dataTransfer对象中删除指定格式的数据,参数可选,若不给出,则为删除对象中所有的数据。

(4)addElement(element):添加自定义图标

(5)setDragImage(element,x,y):设置拖放操作的自定义图标。其中element设置自定义图标,x设置图标与鼠标在水平方向上的距离,y设置图标与鼠标在垂直方向上的距离。

下面展示一个允许通过拖动来添加,删除”收藏夹“功能

 

 

 

(2)原生JS实现

一、拖拽的流程动作

①鼠标按下

②鼠标移动

③鼠标松开

二、拖拽流程中对应的JS事件

①鼠标按下会触发onmousedown事件

obj.onmousedown = function(e) {  
      //..........  
} 

 ②鼠标移动会触发onmousemove事件

obj.onmousemove = function(e) {  
    //......  
}

 ③鼠标松开会触发onmouseup事件

obj.onmouseup = function() {  
     //......  
}

 三、实现的原理讲解

拖拽其实是通过获取鼠标移动的距离来实现的,即计算移动前的位置的坐标(x,y)与移动中的位置的坐标(x,y)差值。

当鼠标按下或鼠标移动时,都可以获取到当前鼠标的位置,即移动前的位置与移动中的位置。

那么上面①与②的代码就应该变成这样

var mouseDownX,mouseDownY  
// 因在移动中需计算鼠标的偏移需要用到鼠标按下时的坐标,固声明称全局变量  
obj.onmousedown = function(e) {  
    mouseDownX = e.pageX;  
    mouseDownY = e.pageY;  
}  
obj.onmousemove = function(e) {  
    var mouseMoveX = e.pageX,mouseMoveY = e.pageY;  
} 

 移动前与移动后坐标有了,那么计算偏移,先看下图

 

很明显移动后元素

X坐标为  鼠标移动后的X坐标 - 鼠标按下的X坐标 + 元素的初始X坐标

Y坐标为  鼠标移动后的Y坐标 - 鼠标按下的Y坐标 + 元素的初始Y坐标

把新的 X,Y 替换元素的 X,Y 就搞定了

那么代码就应该更换为:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>元素拖拽</title>
    <style type="text/css">
        #link{
            position: absolute;
        }
    </style>
</head>
<body>
<p id='link'>我是可以拖拽的</p>
<script type="javascript">
    var obj = document.getElementById('link');
    var mouseDownX,mouseDownY,initX,initY,flag = false;  
    // 因在移动中需计算鼠标的偏移需要用到鼠标按下时的坐标,固声明称全局变量
    obj.onmousedown = function(e) {
    // e为鼠标事件对象,属性pageX为鼠标X坐标,pageY为鼠标Y坐标
        //鼠标按下时所在的X,Y坐标
        mouseDownX = e.pageX;
        mouseDownY = e.pageY;
        //初始位置的X,Y坐标
        initX = obj.offsetLeft;
        initY = obj.offsetTop;
        //鼠标被按下
        flag = true;
    }
    obj.onmousemove = function(e) {
       if(flag){//确认鼠标已按下
           var mouseMoveX = e.pageX,mouseMoveY = e.pageY;
           this.style.left = 
                  parseInt(mouseMoveX) - parseInt(mouseDownX) + parseInt(initX) + "px";
           this.style.top = 
                  parseInt(mouseMoveY) - parseInt(mouseDownY) + parseInt(initY) + "px";
       }else{//未按下鼠标}
    }
    obj.onmouseup = function() {
        //标识已松开鼠标
        flag = false;
    }
</script>
</body>
</html>

 经测试,拖动速度过快时会出现明显卡顿,这个之后再讨论性能优化

需要注意的事:

①如果用jquery库来写的话三个事件为mousedown、mousemove、mouseup,名称稍微有点差别。

②还有一点,被拖拽的元素的样式要设置成绝对或相对位置才有效果。

至此,最简单的元素拖拽功能就讲完了~~~~

如有不正确之处欢迎大家指正!

 

 

 

后续补充下,这里我们讨论下代码的优化:

<!DOCTYPE html>  
<html>  
<head lang="en">  
    <meta charset="UTF-8">  
    <title>元素拖拽</title>  
    <style type="text/css">  
        #xixi {  
            width:200px; height: 200px; position:absolute;  
            left: 50px; top: 50px; background-color: lightcyan;  
        }  
        #haha {  
            position:absolute; left:300px; top:300px;  
            background-color: yellow; width:200px; height: 200px;  
        }  
  
    </style>  
    <script type="text/javascript" src="js/mylib.js"></script>  
    <script type="text/javascript">  
        window.onload = function() {  
            var obj1 = createDraggableObject();  
            var obj2 = createDraggableObject();  
            obj1.init($('xixi'));  
            obj2.init($('haha'));  
        };  
    </script>  
  
</head>  
<body>  
    <div id="xixi">xixi!</div>  
    <div id="haha">haha!</div>  
</body>  
</html>  

 外部JavaScript文件代码如下所示:

/** 
 * 根据id获取页面元素 
 * @param id 
 * @returns {HTMLElement} 
 */  
function $(id) {  
    return document.getElementById(id);  
}  
  
/** 
 * 创建可拖拽对象的工厂方法 
 */  
function createDraggableObject() {  
    return {  
        obj: null, left: 0, top: 0,  
        oldX: 0, oldY: 0, isMouseLeftButtonDown: false,  
        init: function (obj) {  
            this.obj = obj;  
            var that = this;  
            this.obj.onmousedown = function (args) {  
                var evt = args || event;  
                this.style.zIndex = 100;  
                that.isMouseLeftButtonDown = true;  
                that.oldX = evt.clientX;  
                that.oldY = evt.clientY;  
                if (this.currentStyle) {  
                    that.left = parseInt(this.currentStyle.left);  
                    that.top = parseInt(this.currentStyle.top);  
                }  
                else {  
                    var divStyle = document.defaultView.getComputedStyle(this, null);  
                    that.left = parseInt(divStyle.left);  
                    that.top = parseInt(divStyle.top);  
                }  
            };  
            this.obj.onmousemove = function (args) {  
                that.move(args || event);  
            };  
            this.obj.onmouseup = function () {  
                that.isMouseLeftButtonDown = false;  
                this.style.zIndex = 0;  
            };  
        },  
        move: function (evt) {  
            if (this.isMouseLeftButtonDown) {  
                var dx = parseInt(evt.clientX - this.oldX);  
                var dy = parseInt(evt.clientY - this.oldY);  
                this.obj.style.left = (this.left + dx) + 'px';  
                this.obj.style.top = (this.top + dy) + 'px';  
            }  
        }  
    };  
}  

 

 最后展示一个实际应用:通过H5的draggable的APL实现----删除和收藏夹功能

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <title>通过拖放实现添加、删除</title>
    <style type="text/css">
        div>div{
            display:inline-block;
            padding: 10px;
            background-color: #aaa;
            margin: 3px;
        }
    </style>
</head>
<body>
<div style="width:600px;border:1px solid black;">
    <h2>可将喜欢的项目拖到收藏夹</h2>
    <div draggable="true" ondragstart="dsHandler(event);">勿忘心安</div>
    <div draggable="true" ondragstart="dsHandler(event);">照顾自己</div>
    <div draggable="true" ondragstart="dsHandler(event);">Number 9</div>
    <div draggable="true" ondragstart="dsHandler(event);">崇拜</div>
</div>
<div id="dest" style="width:400px;height:400px;border:1px solid black;float:left;">
    <h2 ondragleave="return false;">收藏夹</h2>
</div>
<div id="gb" draggable="false" style="width:100px;height:100px;
       border:1px solid red;float:left;">我是垃圾桶</div>
<script type="text/javascript">
    var dest = document.getElementById("dest");
    var dsHandler = function (evt){
        evt.dataTransfer.setData("text/plain","<item>"+evt.target.innerHTML);
    }

    dest.ondrop = function(evt){
        var text = evt.dataTransfer.getData("text/plain");
        if(text.indexOf("<item>") == 0){
            var newEle = document.createElement("div");
            newEle.id = new Date().getUTCMilliseconds();
            newEle.innerHTML = text.substring(6);
            newEle.draggable = "true";
            newEle.ondragstart = function(evt){
                evt.dataTransfer.setData("text/plain","<remove>"+newEle.id);
            }
            dest.appendChild(newEle);
        }
    }

    document.getElementById("gb").ondrop = function(evt){
        var id = evt.dataTransfer.getData("text/plain");
        if(id.indexOf("<remove>") == 0){
            var target = document.getElementById(id.substring(8));
            dest.removeChild(target);
        }
    }

    document.ondragover = function(evt){
        return false;
    }

    document.ondrop = function(evt){
        return false;
    }
</script>
</body>
</html>

 

 

 

 

 

.

 

猜你喜欢

转载自570109268.iteye.com/blog/2408954