拖放是由拖动与释放两部分组成,拖放事件也分为被拖动元素的相关事件,和容器的相关事件。 被拖动元素的相关事件如下所示:
被拖动元素相关事件:
| 事件 | 描述 |
|---|---|
| dragstart | 用户开始拖动元素时触发 |
| drag | 元素正在拖动时触发 |
| dragend | 用户完成元素拖动后触发 |
容器相关事件如下所示:
| 事件 | 描述 |
|---|---|
| dragenter | 当被鼠标拖动的对象进入目标容器时触发此事件 |
| dragover | 当被拖动的对象在目标容器范围内拖动时触发此事件 |
| dragleave | 当被鼠标拖动的对象离开目标容器时触发此事件 |
| drop | 在一个拖动过程中,释放鼠标键到目标容器时触发此事件 |
1.首先给标签添加draggable="true"属性表明标签可拖拽。
<div draggable="true" id="7">7div>
2.通过标签绑定dragstart事件,会在用户拖动标签时开始触发。我们将拖拽标签的id存入其中。
- // 拖动元素开始触发
- div.ondragstart = function (e) {
- e.dataTransfer.setData('Text', e.target.id)
- oldPar = div.parentNode // 记录父节点
- }
3.在放置拖动元素的容器上绑定一个 dragover 事件,这个事件用于规定在何处放置被拖动的数据。默认情况下,是无法将一格元素放置到另外一个元素里面的,所以如果需要设置允许放置,则要在 ondragover 事件中加上 e.preventDefault() 方法来阻止默认行为。
- dragBox.ondragover = function (e) {
- e.preventDefault()
- }
4. 将元素拖拽放置在容器中(在容器中松开鼠标),在在容器中绑定一个drop 事件中,同样需要调用 e.preventDefault() 方法来阻止默认行为。然后可以通过 dataTransfer.getData("Text"); 方法获取之前的 drag(event) 函数中保存的信息,也就是被拖动元素的 id。接着通过 容器dom实例.appendChild() 方法为将拖动元素作为元素容器的子元素追加到元素容器中,这样就能成功实现拖放。
- // 容器内放置拖动元素
- dragBox.ondrop = function (e) {
- e.preventDefault()
- let data = e.dataTransfer.getData("Text") // 这么说这是一个静态对象
- let newChil = document.getElementById(data)
- dragBox.appendChild(newChil) // 追加子元素
-
- }
效果展示

代码实现
- html>
- <html>
-
- <head>
- <meta charset="utf-8">
- <title>拖拽title>
- <style type="text/css">
- .drag-box {
- display: flex;
- }
-
- .drag-box ul div {
- margin: 10px 0;
- width: 100px;
- height: 100px;
- border: 1px solid black;
- text-align: center;
- line-height: 100px;
- font-size: 20px;
- font-weight: bolder;
- background-image: linear-gradient(transparent,
- rgba(0, 0, 0, 0.5));
- transition: all 2s;
- }
- style>
- head>
-
- <body>
- <div class="drag-box">
- <ul>
- <div draggable="true" id="1">1div>
- ul>
- <ul>
- <div draggable="true" id="2">2div>
- ul>
- <ul>
- <div draggable="true" id="3">3div>
- ul>
- <ul>
- <div draggable="true" id="4">4div>
- ul>
- <ul>
- <div draggable="true" id="5">5div>
- ul>
- <ul>
- <div draggable="true" id="6">6div>
- ul>
- <ul>
- <div draggable="true" id="7">7div>
- ul>
- <ul>
- <div draggable="true" id="8">8div>
- ul>
- <ul>
- <div draggable="true" id="9">9div>
- ul>
- div>
- body>
- <script>
- let dragBoxs = document.querySelectorAll('.drag-box>ul')
- let divs = document.querySelectorAll('.drag-box div')
- let oldPar;
- for (const div of divs) {
- // 拖动元素开始触发
- div.ondragstart = function (e) {
- e.dataTransfer.setData('Text', e.target.id)
- oldPar = div.parentNode // 记录父节点
- }
- // 拖动元素过程中触发
- div.ondrag = function (e) {
- e.target.style.opacity = "0"
- oldPar.style.display = "none"
- }
- // 拖动元素完成后触发
- div.ondragend = function (e) {
- e.target.style.opacity = "1"
- console.log(1);
- oldPar.style.display = "block"
- }
- }
-
- // 容器事件
- for (const dragBox of dragBoxs) {
- // 拖动元素在目标容器内触发
- dragBox.ondragover = function (e) {
- e.preventDefault()
- }
- // 容器内放置拖动元素
- dragBox.ondrop = function (e) {
- e.preventDefault()
- let data = e.dataTransfer.getData("Text") // 这么说这是一个静态对象
- let newChil = document.getElementById(data)
- let oldChil = dragBox.children[0]
- // 判断盒子是否空元素
- if (oldChil) {
- dragBox.appendChild(newChil) // 追加新的子元素,删除旧的子元素
- dragBox.removeChild(oldChil)
- oldPar.appendChild(oldChil)
- } else {
- dragBox.appendChild(newChil)
- }
-
- }
- }
-
-
-
-
- script>
-
- html>
我们直接用一个案例来解释:
效果展示

ps: 还是有bug,目前找不出来。
我们实现这个功能,需要3个事件:
前提: 子元素是相对定位,容器是绝对定位,脱离文档流。
1.按住鼠标 (mousedown),获取当前元素 offsetLeft 和offsetTop,当前鼠标的clientX、clientY,用鼠标坐标 - 元素的offsetLeft、offsetTop属性,求出鼠标-元素边界的距离.
2.移动鼠标(monusemove),获取移动元素中鼠标的clientX、clientY 减去上边求出的鼠标到元素边界的距离 ,求出元素移动top、left值,赋给元素。
3.松开鼠标(monuseup),将鼠标移动事件清除清除。
下面有2个元素,判断2个元素是否碰撞,如果碰撞的情况非常多,我们可以考虑没碰撞的情况,下面满足任意一种情况就是没碰撞的。

实现代码
我们通过检测元素是否碰撞,元素之间间隙到一定距离;就将空隙距离返回。
- // 碰撞检测事件
- // 拖拽元素为node2
- function knock(node1, node2) {
- var l1 = node1.offsetLeft;
- var r1 = node1.offsetLeft + node1.offsetWidth;
- var t1 = node1.offsetTop;
- var b1 = node1.offsetTop + node1.offsetHeight;
- var l2 = node2.offsetLeft;
- var r2 = node2.offsetLeft + node2.offsetWidth;
- var t2 = node2.offsetTop;
- var b2 = node2.offsetTop + node2.offsetHeight;
- // 左空隙
- let _left = l2 - r1
- // 右空隙
- let _right = l1 - r2
- // 上空隙
- let _top = t2 - b1
- // 下空隙
- let _bottom = t1 - b2
- // 没碰撞
- let obj = {};
- // 没碰撞;大于1px 小于15px应该顶撞他;其余情况没考虑,统一设为 0px(为啥1 -15 之间,鼠标移动太快,检测有延迟)
- if (l2 > r1 || r2 < l1 || t2 > b1 || b2 < t1) {
-
- // 左空隙 >=10 <=20
- if (_left >= 1 && _left <= 15) {
- obj.left = _left
- } else {
- obj.left = 0
- }
- // 右空隙 大于10
- if (_right >= 1 && _right <= 15) {
- obj.right = _right
- } else {
- obj.right = 0
- }
- // 上空隙 大于10
- if (_top >= 1 && _top <= 15) {
- obj.top = _top
- } else {
- obj.top = 0
- }
- // 下空隙 大于10
- if (_bottom >= 1 && _bottom <= 15) {
- obj.bottom = _bottom
- } else {
- obj.bottom = 0
- }
- return obj
- } else {
- // 上面理论上,元素之间有空隙;
- return true
- }
设置bd函数, 将拖拽div,和div数组传入,遍历div数组,然后调用上面knock()方法,来判断是否碰撞;
将拖拽div距离其他标签的div句空隙距离,将距离设置给其他div,从而实现碰撞撞开其他元素。
- // 给拖拽中div绑定碰撞函数,并根据返回值,拖拽div与其他div之间隔出空隙
- function bd(div1, divs) {
- let c_divs = []
- for (const div of divs) {
- c_divs.push(div)
- }
- // 遍历删除div1
- divs1 = c_divs.filter(div =>
- div1 != div
- )
- // 记录与他碰撞的div元素和是否碰撞
- for (const div of divs1) {
- let item = {}
- let isKnock = knock(div, div1)
- // 拿出2个div间隙
- if (isKnock != true) {
- let { left, right, top, bottom } = isKnock
- let { offsetLeft, offsetTop, offsetHeight, offsetWidth } = div
-
- if (left > 0) {
- let _left;
- let left1 = offsetLeft - left;
- _left = left1 >= 0 ? left1 : _left
- div.style.left = _left + 'px'
- }
- if (right > 0) {
- let _right;
- let right1 = div1.offsetLeft + div1.offsetWidth + right;
- _right = right1 <= body.offsetWidth - offsetWidth ? right1 : _right
- div.style.left = _right + 'px'
- }
- if (top > 0) {
- let _top;
- let top1 = offsetTop - top
- _top = top1 >= 0 ? top1 : _top
- div.style.top = _top + 'px'
- }
- if (bottom > 0) {
- let _bottom;
- let bottom1 = div1.offsetTop + div1.offsetHeight + bottom
- _bottom = bottom1 <= body.offsetHeight - offsetHeight ? bottom1 : _bottom
- div.style.top = _bottom + 'px'
- }
- }
- }
- }
完整代码
- html>
- <html lang="en">
-
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <style>
- body {
- position: absolute;
- height: 98vh;
- width: 98vw;
- border: 2px solid black;
- }
-
- div {
- position: absolute;
- margin: 5px;
- width: 100px;
- height: 100px;
- background-color: rebeccapurple;
-
-
- }
- style>
- <title>页面拖动和碰撞title>
- head>
-
- <body>
- <div>1div>
- <div>2div>
- <div>3div>
-
- body>
- <script>
-
- let divs = document.getElementsByTagName("div")
- let body = document.getElementsByTagName("body")[0]
-
-
- for (const div of divs) {
- drag(div, divs)
- // 帮主div绑定与其他div的碰撞事件
- }
- // for in 只能遍历对象
-
- // 给div 和其他div绑定碰撞函数
- function bd(div1, divs) {
- let c_divs = []
- for (const div of divs) {
- c_divs.push(div)
- }
- // 遍历删除div1
- divs1 = c_divs.filter(div =>
- div1 != div
- )
- // 记录与他碰撞的div元素和是否碰撞
- for (const div of divs1) {
- let item = {}
- let isKnock = knock(div, div1)
- // 拿出2个div间隙
- let { left, right, top, bottom } = isKnock
- // div
- let { offsetLeft, offsetTop, offsetHeight, offsetWidth } = div
-
- if (left > 0) {
- let _left;
- let left1 = offsetLeft - left;
- _left = left1 >= 0 ? left1 : _left
- div.style.left = _left + 'px'
- }
- if (right > 0) {
- let _right;
- let right1 = div1.offsetLeft + div1.offsetWidth + right;
- _right = right1 <= body.offsetWidth - offsetWidth ? right1 : _right
- div.style.left = _right + 'px'
- }
- if (top > 0) {
- let _top;
- let top1 = offsetTop - top
- _top = top1 >= 0 ? top1 : _top
- div.style.top = _top + 'px'
- }
- if (bottom > 0) {
- let _bottom;
- let bottom1 = div1.offsetTop + div1.offsetHeight + bottom
- _bottom = bottom1 <= body.offsetHeight - offsetHeight ? bottom1 : _bottom
- div.style.top = _bottom + 'px'
- }
- }
- }
-
- // js拖动事件
- function drag(obj, divs) {
- //当鼠标在被拖拽元素上按下,开始拖拽
- obj.onmousedown = function (event) {
- event = event || window.event;
- //鼠标在元素中的偏移量等于 鼠标的clientX - 元素的offsetLeft
- let { offsetLeft, offsetTop } = obj
- // 设置小于0判断,防止移除边界
- var ol = event.clientX - offsetLeft;
- var ot = event.clientY - offsetTop
-
- let _left = 0;
- let _top = 0;
- //为document绑定一个onmousemove事件,鼠标移动事件
- document.onmousemove = function (event) {
- bd(obj, divs) // 元素移动调用碰撞函数
- event = event || window.event;
- var left = event.clientX - ol;
- var top = event.clientY - ot;
- let { offsetWidth, offsetHeight } = body
- // 防止元素移出容器
- _left = left >= 0 && left <= offsetWidth - obj.offsetWidth ? left : _left
- _top = top >= 0 && top <= offsetHeight - obj.offsetHeight ? top : _top
-
- //修改元素的位置 修改元素的位置只能通过 元素.style.属性 = "属性值";
- // 判断 > =0
- obj.style.left = _left + "px";
- obj.style.top = _top + "px";
- };
- //为document绑定一个鼠标松开事件onmouseup
- document.onmouseup = function () {
- document.onmousemove = null;
- document.onmouseup = null;
- };
- return false;
- };
- }
-
- // 碰撞检测事件
- // 拖拽元素为node2
- function knock(node1, node2) {
- var l1 = node1.offsetLeft;
- var r1 = node1.offsetLeft + node1.offsetWidth;
- var t1 = node1.offsetTop;
- var b1 = node1.offsetTop + node1.offsetHeight;
- var l2 = node2.offsetLeft;
- var r2 = node2.offsetLeft + node2.offsetWidth;
- var t2 = node2.offsetTop;
- var b2 = node2.offsetTop + node2.offsetHeight;
- // 左空隙
- let _left = l2 - r1
- // 右空隙
- let _right = l1 - r2
- // 上空隙
- let _top = t2 - b1
- // 下空隙
- let _bottom = t1 - b2
- // 没碰撞
- let obj = {};
- // 没碰撞;大于1px 小于15px应该顶撞他;其余情况没考虑,统一设为 0px(为啥1 -15 之间,鼠标移动太快,检测有延迟)
- if (l2 > r1 || r2 < l1 || t2 > b1 || b2 < t1) {
-
- // 左空隙
- if (_left >= 1 && _left <= 15) {
- obj.left = _left
- } else {
- obj.left = 0
- }
- // 右空隙
- if (_right >= 1 && _right <= 15) {
- obj.right = _right
- } else {
- obj.right = 0
- }
- // 上空隙
- if (_top >= 1 && _top <= 15) {
- obj.top = _top
- } else {
- obj.top = 0
- }
- // 下空隙
- if (_bottom >= 1 && _bottom <= 15) {
- obj.bottom = _bottom
- } else {
- obj.bottom = 0
- }
- return obj
- } else {
- // 上面理论上,元素之间有空隙;
- return true
- }
- }
-
-
- script>
-
- html>
参考资料: https://blog.csdn.net/horizon12/article/details/108650346