探讨把一个元素从它所在的div 拖动到另一个div...

452 阅读5分钟

故事背景:
接到一个新需求,要求用vue搞,主要是拖动实现布局,关键点有:单个组件拖动,一行多列里面的组件拖动, 单个组件可以拖入一行多列里, 单个组件的拖动好实现,关键是把一个组件拖动到另一个类似于表格里面,而且有的情况下还需要限制拖动只能在水平方向,自己搜集资料, 实验,终于搞出来了。

原理上主要分为两类:
HTML5自带的拖放api,可用的库有 : Vue.Draggable
使用js 监听鼠标的移动位置, 可用的库有: jquery ui
使用point-event: none(下面会详细说明)
各自缺点
H5拖动的缺点:不能限制在水平 或垂直方向上拖动。

使用原生js缺点:大量的dom操作,代码复杂(jquery ui 封装的比较好了,可直接用)

但是问题来了,这次的需求是把基于jquery ui 的拖动 用 vue 重构,那么用vue.draggable吧, 但是需求里正好有一条是要限制在水平方向上拖动,尴尬,用不了。

寻找方案
最开始的尝试:

[HTML]
纯文本查看
复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
<!DOCTYPE html>
<html>
<head>
<title>vue结合原生js实现拖动</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
</head>
<body>
<div id="app">
<div class="ctn ctn1">
<div class="sub sub1" v-for="(site, index) in list1">
<div class="dragCtn fixed" :style="{ left: X+'px', top: Y+'px'}"
@mousedown="mousedown(site, $event)"
@mousemove.prevent='mousemove(site, $event)'
@mouseup='mouseup(site, $event)'>
拖动我
</div>
</div>
</div>
<div class="ctn ctn2">
<div class="sub sub2" v-for="(site, index) in list2"
@mouseenter='mouseenter(site, $event)'>
<div class="dragCtn">
{{ index }} : {{ site.name }}
</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
list1: [{name:1, index:0}],
list2: [{name:'a', index:0}, {name:'b', index:1}, {name:'c', index: 2}, {name:'d', index: 3}],
vm:'',
sb_bkx: 0,
sb_bky: 0,
is_moving: false,
X: 0,
Y: 0
},
methods: {
mousedown: function (site, event) {
var startx=event.x;
var starty=event.y;
this.sb_bkx=startx - event.target.offsetLeft;
this.sb_bky=starty - event.target.offsetTop;
this.is_moving = true;
},
mousemove: function (site, event) {
var endx=event.x - this.sb_bkx;
var endy=event.y - this.sb_bky;
var _this = this
if(this.is_moving){
this.X = endx;
this.Y = endy;
}
},
mouseup: function (e) {
this.is_moving = false;
},
mouseenter: function (){
console.log('鼠标进入')
}
}
})
</script>
<style>
.ctn{
line-height: 50px;
cursor: pointer;
font-size: 20px;
text-align: center;
float: left;
}
.sub:hover{
background: #e6dcdc;
color: white;
width: 100px;
}
.ctn1{
border: 1px solid green;
width: 100px;
}
.ctn2{
border: 1px solid black;
width: 100px;
margin-left: 50px;
}
.fixed{
width: 100px;
position: fixed;
background: red;
left: 0;
top: 0;
cursor: move;
}
</style>
</body>
</html>



就这样实现了基本的拖动,但是在拖动的时候,就不能触 mouseenter 事件了,而且鼠标必须拖动的很慢,移动快一点,拖动的div就跟不上了,这一点到现在还困惑,希望各位大侠指点。然后在网上找了很多资料,类库,但是不能完全符合我的需求,于是准备自己写了;

首先是给组件添加mousedown事件,然后mousemove的时候 监听鼠标的位置,再赋值给组件,实现拖动,但是当组件拖动进入另一个元素的时候,无法监听mouseenter, 后来想的办法是给正在拖动的组件加上 point-event:none 属性,就是消除原有的鼠标事件,就可以触发其他组件的mouerenter事件了,point-event 属性的具体用法 可以参考这里:

www.zhangxinxu.com/wordpress/2

因为拖动是用原生js写的,所以可以限制在水平方向拖动,再加上可以触发mouseenter事件,就正好实现的我的需求。

伪代码如下:

[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
mousedown: function (event, site) {
document.onmousemove=function (ev) {
// 移动的时候给元素增加 point-event:none 属性
...
}
document.onmouseup=function (ev) {
// up的时候 要移除point-event属性
...
}
}


但是后来上面要求,要兼容ie10,由于 point-event:none 是H5的属性,于是我赶紧去看看兼容性, 可怕的事发生了,point-event 属性只兼容到 ie11,完蛋!

再想其他办法吧,没了思路,老版本的拖动是基于 jquery ui 的 ,于是去看了 jquery ui 的源码,看看它的拖动是怎么实现的。

jquery ui 拖动实现原理
不熟悉 jquery ui 的拖动方法的可以先看下 这里

看下面这段代码:

[JavaScript]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
$(function() {
$( "#draggable" ).draggable();
$( "#droppable" ).droppable({
drop: function( event, ui ) {
$( this ).html( "Dropped!" );
}
});
});


之前的关键问题就是怎么判断拖动的元素 $( "#draggable" ) 在什么时候 进入了可以放置的区域 $( "#droppable" ) 的,看了源码,它的实现方式 简单来说就是拖动 $( "#draggable" ) 的时候监听 鼠标的位置, 同时获取 $( "#droppable" ) 的区域位置信息,只要鼠标进入该区域,就触发。

顺着这个思路,做了实验 可以满足自己的需求 good !

demo的实现效果如下:



代码地址: github.com/YalongYan/v…

拖动布局的实现方式应该还有更好的,欢迎大家提出更好的实现方式。