专栏首页前端菜鸟变老鸟彻底搞懂拖拽——基于鼠标事件的拖拽以及基于HTML5 API的拖拽完整实现

彻底搞懂拖拽——基于鼠标事件的拖拽以及基于HTML5 API的拖拽完整实现

版权声明:本文为博主原创文章,未经博主允许不得转载。

一、基于鼠标事件的拖拽

原理——onmousedown、onmousemove、onmouseup

  1. onmousedown
  • 该事件会在鼠标按键被按下时触发
  • 支持该事件的HTML标签:
   <a>, <address>, <area>, <b>, <bdo>, <big>, <blockquote>, <body>, <button>,
   <caption>, <cite>, <code>, <dd>, <dfn>, <div>, <dl>, <dt>, <em>, <fieldset>,  
   <form>, <h1> to <h6>, <hr>, <i>, <img>, <input>, <kbd>, <label>, <legend>,  
   <li>, <map>, <ol>, <p>, <pre>, <samp>, <select>, <small>, <span>, <strong>,  
   <sub>, <sup>, <table>, <tbody>, <td>, <textarea>, <tfoot>, <th>, <thead>,  
   <tr>, <tt>, <ul>, <var>
  • 支持该事件的JavaScript对象: button, document, link
  1. onmousemove
  • 该事件会在鼠标指针移动时触发
  • 支持该事件的HTML标签:
   <a>, <address>, <area>, <b>, <bdo>, <big>, <blockquote>, <body>, <button>,  
   <caption>, <cite>, <code>, <dd>, <dfn>, <div>, <dl>, <dt>, <em>, <fieldset>,  
   <form>, <h1> to <h6>, <hr>, <i>, <img>, <input>, <kbd>, <label>, <legend>,  
   <li>, <map>, <ol>, <p>, <pre>, <samp>, <select>, <small>, <span>, <strong>,  
   <sub>, <sup>, <table>, <tbody>, <td>, <textarea>, <tfoot>, <th>, <thead>,  
   <tr>, <tt>, <ul>, <var>
  • 支持该事件的JavaScript对象: 默认情况下,onmousemove不是任何对象的事件,因为鼠标移动非常频繁
  1. onmouseup
  • 该事件会在鼠标按键被松开时触发
  • 支持该事件的HTML标签:
   <a>, <address>, <area>, <b>, <bdo>, <big>, <blockquote>, <body>, <button>,  
   <caption>, <cite>, <code>, <dd>, <dfn>, <div>, <dl>, <dt>, <em>, <fieldset>,  
   <form>, <h1> to <h6>, <hr>, <i>, <img>, <input>, <kbd>, <label>, <legend>,  
   <li>, <map>, <ol>, <p>, <pre>, <samp>, <select>, <small>, <span>, <strong>,  
   <sub>, <sup>, <table>, <tbody>, <td>, <textarea>, <tfoot>, <th>, <thead>,  
   <tr>, <tt>, <ul>, <var>
  • 支持该事件的JavaScript对象: button, document, link

具体实现

code:

<!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>
    <style>
        #test {
            width: 100px;
            height: 100px;
            background: #000;
            position: absolute;
            color: #fff;
        }
    </style>
</head>

<body>
    <div id="test">4616125</div>
    <script>
        (function() {
            function Code() {}
            Code.prototype = {
                addEvent: function() {
                    var that = this;
                    var oDiv = document.getElementById('test');
                    oDiv.onmousedown = function(ev) {
                        var ev = ev || event;
                        var distanceX = ev.clientX - this.offsetLeft;
                        var distanceY = ev.clientY - this.offsetTop;
                        if (oDiv.setCapture) {
                            oDiv.setCapture();
                        }
                        document.onmousemove = function(ev) {
                            var ev = ev || event;
                            oDiv.style.left = ev.clientX - distanceX + 'px';
                            oDiv.style.top = ev.clientY - distanceY + 'px';
                        };
                        document.onmouseup = function(ev) {
                            document.onmousemove = document.onmouseup = null;
                            if (oDiv.releaseCapture) {
                                oDiv.releaseCapture();
                            }
                        };
                    };
                },

                init: function() {
                    var that = this;
                    window.onload = that.addEvent;
                },
            }
            new Code().init();
        })();
    </script>
</body>

</html>

注意事项以及存在的问题:

  1. 注意事项
  • 被拖动的div的position属性值一定是absolute
  • onmousedown事件需要在window.onload时加载
  • 如果被拖动的div上有文字会有自带的文字拖动效果,需要将改div上的所有拖动事件绑定在该div上,可以使用setCapture
  • onmousemove和onmouseup需要在onmousedown里面绑定
  1. 存在的问题
  • 会被拖出边界

解决方案

  • 只需要实时计算拖拽的元素边框距离上下左右屏幕之间的距离就行了,具体代码如下:

code:

<!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>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        #oDiv {
            width: 100px;
            height: 100px;
            background-color: #000;
            position: absolute;
        }
    </style>
</head>

<body>
    55555555555
    <div id="oDiv"></div>
    <script>
        oDiv.onmousedown = function(e) {
            var ev = e || event;
            var left = ev.clientX - oDiv.offsetLeft,
                top = ev.clientY - oDiv.offsetTop;
            document.onmousemove = function(e) {
                var ev = e || event;
                var leftW = ev.clientX - left;
                var topH = ev.clientY - top;
                //左边不能超出
                if (leftW < 0) {
                    leftW = 0;
                }
                //上边不能超出
                if (topH < 0) {
                    topH = 0;
                }
                //右边不能超出
                if (leftW > document.documentElement.clientWidth - oDiv.offsetWidth) {
                    leftW = document.documentElement.clientWidth - oDiv.offsetWidth;
                }
                //下边不能超出
                if (topH > document.documentElement.clientHeight - oDiv.offsetHeight) {
                    topH = document.documentElement.clientHeight - oDiv.offsetHeight;
                }
                oDiv.style.left = leftW + 'px';
                oDiv.style.top = topH + 'px';
            }
            document.onmouseup = function(e) {
                document.onmousemove = null;
                document.onmouseup = null;
            }
            return false;
        }
    </script>
</body>

</html>

result:

至此使用鼠标事件的拖拽大功告成!


二、基于HTML5拖拽API的拖拽

前序知识介绍

  一个典型的拖拽操作是这样的:用户用鼠标选中一个可拖动的(draggable)元素,移动鼠标到一个可放置的(droppable)元素,然后释放鼠标。 在操作期间,会触发一些事件类型,有一些事件类型可能会被多次触发(比如drag 和 dragover 事件类型)。   这里涉及几个知识点:

  1. 可拖动元素: 又称为源对象,是指我们鼠标点击之后准备拖动的对象(图片、div、文字等)
  2. 可放置元素: 又称为目标对象,是指可以放置源对象的区域
  3. 事件:

Event

On Event Handler

Description

drag

ondrag

当拖动元素或选中的文本时触发

dragend

ondragend

当拖拽操作结束时触发 (比如松开鼠标按键或敲“Esc”键)

dragenter

ondragenter

当拖动元素或选中的文本到一个可释放目标时触发

dragexit

ondragexit

当元素变得不再是拖动操作的选中目标时触发

dragleave

ondragleave

当拖动元素或选中的文本离开一个可释放目标时触发

dragover

ondragover

当元素或选中的文本被拖到一个可释放目标上时触发

dragstart

ondragstart

当用户开始拖动一个元素或选中的文本时触发

drop

ondrop

当元素或选中的文本在可释放目标上被释放时触发

ps:当从操作系统向浏览器中拖动文件时,不会触发dragstart 和dragend 事件

  1. 接口:

HTML5为所有的拖动相关事件提供了一个新的属性:

  • 源对象和目标对象的事件间传递数据 ev.dataTransfer {}//数据传递对象
  • 源对象上的事件处理中保存数据: ev.dataTransfer.setData(key,value);//key,value必须都是字符串类型
  • 目标对象上的事件处理中读取数据: var value2 = ev.dataTransfer.getData(key);
  1. 兼容性

ps:图片来源(CAN I USE?


具体实现代码

code:

<!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>
    <style type="text/css">
        #thatDiv {
            width: 500px;
            height: 100px;
            border: 1px solid red;
            position: relative;
        }
        #thisDiv {
            width: 500px;
            height: 100px;
            border: 1px solid black;
            margin-bottom: 20px;
        }
        #tarDiv,
        #tarDiv1,
        #tarDiv2,
        #tarDiv3,
        #tarDiv4 {
            float: left;
            width: 50px;
            height: 50px;
            background-color: #000;
            border: 1px #fff solid;
        }
        .tarDiv {
            color: #fff;
            text-align: center;
            line-height: 50px;
        }
    </style>
</head>

<body>
    <div id="thisDiv">
        <div id="tarDiv" class="tarDiv" draggable="true">1</div>
        <div id="tarDiv1" class="tarDiv" draggable="true">2</div>
        <div id="tarDiv2" class="tarDiv" draggable="true">3</div>
        <div id="tarDiv3" class="tarDiv" draggable="true">4</div>
        <div id="tarDiv4" class="tarDiv" draggable="true">5</div>
    </div>
    <div id="thatDiv"></div>

    <script type="text/javascript">
        var tarDiv = document.getElementsByClassName("tarDiv");
        var thisDiv = document.getElementById("thisDiv");
        var thatDiv = document.getElementById("thatDiv");
        thisDiv.ondragstart = function(ev) {
            var ev = ev || window.event;
            ev.dataTransfer.setData("text", ev.target.id); //将被拖拽的元素的id存入dataTransfer对象中
            window.thisId = ev.target.id;
            ev.dataTransfer.effectAllowed = "copy";
        }
        thatDiv.ondragover = function(ev) { //阻止dragover的默认事件
            var ev = ev || window.event;
            if (typeof ev.preventDefault == "function") {
                ev.preventDefault();
            } else {
                ev.returnValue = false;
            }
            var div = document.getElementById(window.thisId);
            thatDiv.appendChild(div);
            div.style.cssText = "border:1px #fff dashed;";

            ev.preventDefault();
            ev.dataTransfer.dropEffect = "copy";
        }
        thatDiv.ondragenter = function(ev) { //阻止dragenter的默认事件
            var ev = ev || window.event;
            if (typeof ev.preventDefault == "function") {
                ev.preventDefault();
            } else {
                ev.returnValue = false;
            }
        }
        thatDiv.ondragleave = function(ev) {
            var ev = ev || window.event;
            var removeDiv = document.getElementById(window.thisId);
            thatDiv.removeChild(removeDiv);
            thisDiv.appendChild(removeDiv);
            removeDiv.style.cssText = "border:1px #fff solid;";
            ev.preventDefault();
        }
        thatDiv.ondrop = function(ev) {
            var ev = ev || window.event;
            var divId = ev.dataTransfer.getData("Text"); //从dataTransfer对象中取出数据
            if (typeof ev.preventDefault == "function") { //阻止drop事件的默认行为
                ev.preventDefault();
            } else {
                ev.returnValue = false;
            }
            var moveDiv = document.getElementById(divId);
            thatDiv.appendChild(moveDiv);
            moveDiv.setAttribute('draggable', 'false');
            moveDiv.style.cssText = "border:1px #fff solid;";

        }
    </script>
</body>

</html>

result:


参考资料

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • div拖拽实现延伸

    从入门到进错门
  • js实现截图并保存图片(html转canvas、canvas转image)

    从入门到进错门
  • WEB性能(6)--经典最佳实践

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    从入门到进错门
  • JS示例34-鼠标跟随事件

    1、onmousemove 鼠标移动事件(要考虑页面滚动) 2、var X = ev.clientX; 3、var Y = ev.clientY + scr...

    专注APP开发
  • div拖拽实现延伸

    从入门到进错门
  • 原生JS实现百叶窗特效

    越陌度阡
  • 前端|手风琴--抽屉式网页特效

    我们在浏览网页时或者翻转页面时,经常会看到很多精美的效果的效果。其中,抽屉式网页效果比较常见,它主要由图片和文字通过添加js来实现。抽屉式网页通过移动鼠标来实现...

    算法与编程之美
  • Hello React

      至此,Mac环境下react基于脚手架的开发环境已搭建完成。运行项目( npm start)后,浏览器会自动打开本地页面http://localhost:3...

    流眸
  • Android事件分发简单梳理

    当我们的手指在android手机上点按或者滑动时会触发一系列的事件流,你可能触摸到的是一个Button或者是一个Layout,那么这些事件是怎么一级级传递,哪些...

    大话swift
  • NEC html规范

    HTML规范 - 整体结构 HTML基础设施 文件应以“<!DOCTYPE ......>”首行顶格开始,推荐使用“<!DOCTYPE html>”。 必须申明...

    用户1197315

扫码关注云+社区

领取腾讯云代金券