1.实现目标:目标是输入一个数组,生成一个列表;通过拖拽排序,拖拽结束后输出一个经过排序的数组。
2.实现思路:
2.1是使用HTML5的drag功能来实现,每次拖拽时直接操作Dom节点排序,拖拽结束后再根据实际的dom节点遍历得出新的数组。
2.2使用mousedown,mouseover等鼠标事件来实现,每次监听事件时,仅改动列表项的样式transform,而不操作实际的dom顺序。拖拽结束时,根据transform计算数组项顺序,得出新数组用vue数据驱动的方式重绘列表,重置所有样式。
总的来说就是可以通过不同的监听事件(drag、mouseover),按不同的顺序操作Dom(1.先操作实际dom,再添加动画,在输出数组;2。不操作实际dom,仅改变transfrom,得出新数组,用新数组生成新列表来更新节点)。
3.实际代码
3.1第一种实现
html部分。(被拖拽的元素需要设置draggable=true,否则不会有效果)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
< div id="app"> < ul @dragstart="onDragStart" @dragover="onDragOver" @dragend="onDragEnd" ref="parentNode"> < li v-for="(item,index) in data" :key="index" class="item" draggable="true" >{{item}}</ li > </ ul > </ div > |
拖拽事件有两个对象(被拖拽对象和目标对象)。dragstart 事件: 当拖拽元素开始被拖拽的时候触发的事件,此事件作用在被拖拽元素上。dragover事件:当拖拽元素穿过目标元素时候触发的事件,此事件作用在目标元素上。
在拖拽事件开始时,将本次拖拽的对象保存到变量中。每当dragover事件,将目标对象保存到变量中,添加判断当目标对象和拖拽对象为不同的列表项时,交换两个dom元素的先后顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
onDragStart(event){ console.log("drag start") this.draging=event.target; }, onDragOver(event){ console.log('drag move') this.target=event.target; if (this.target.nodeName === "LI" && this.target !== this.draging) { if(this._index(this.draging)< this._index (this.target)){ this.target.parentNode.insertBefore(this.draging,this.target.nextSibling); }else{ this.target.parentNode.insertBefore(this.draging,this.target); } } }, onDragEnd(event){ console.log('drag end') let currentNodes=Array.from(this.$refs.parentNode.childNodes); let data=currentNodes.map((i,index)=>{ let item=this.data.find(c=>c==i.innerText); return item }); console.log(data) }, _index(el){ let domData=Array.from(this.$refs.parentNode.childNodes); return domData.findIndex(i=>i.innerText==el.innerText); } |
现在基本效果有了,然后是添加动画。添加动画的方式是通过transform实现。
因为每次拖拽排序触发时都会改变dom结构,为了实现移动的效果,可以在每次排序时先将dom节点恢复通过transform到原来的位置,使得表现上还是排序前的状态。然后添加transition,同时置空transform实现移动效果。(这里需要重绘才能触发效果,否则两次transform会直接抵消掉,可以使用setTimeout或者ele.offsetWidth来触发重绘),transform的偏移量可以通过改变节点顺序前后的距顶高度来获得。
完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
<! DOCTYPE html> < html > < head > < meta charset="utf-8"> < meta name="viewport" content="width=device-width,initial-scale=1.0"> < script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></ script > < style > ul{ list-style:none; padding-bottom:20px; } .item{ cursor: pointer; height:24px; line-height:24px;
border:1px solid #d9d9d9; border-radius:4px; color:#fff; padding:10px; } </ style > </ head > < body > < div id="app"> < ul @dragstart="onDragStart" @dragover="onDragOver" @dragend="onDragEnd" ref="parentNode"> < li v-for="(item,index) in data" :key="index" class="item" draggable="true" >{{item}}</ li > </ ul > </ div > </ body > < script > var app = new Vue({ el: '#app', data: { data:[1,2,3,4,5,6], draging:null,//被拖拽的对象 target:null,//目标对象 }, mounted () { //为了防止火狐浏览器拖拽的时候以新标签打开,此代码真实有效 document.body.ondrop = function (event) { event.preventDefault(); event.stopPropagation(); } }, methods:{ onDragStart(event){ console.log("drag start") this.draging=event.target; }, onDragOver(event){ console.log('drag move') this.target=event.target; let targetTop=event.target.getBoundingClientRect().top; let dragingTop=this.draging.getBoundingClientRect().top; if (this.target.nodeName === "LI"&&this.target !== this.draging) { if (this.target) { if (this.target.animated) { return; } } if(this._index(this.draging)<this._index(this.target)){ this.target.parentNode.insertBefore(this.draging,this.target.nextSibling); }else{ this.target.parentNode.insertBefore(this.draging, this.target); } this._anim(targetTop,this.target); this._anim(dragingTop,this.draging); } }, _anim(startPos,dom){ let offset=startPos-dom.getBoundingClientRect().top; dom.style.transition="none"; dom.style.transform=`translateY(${offset}px)`; //触发重绘 dom.offsetWidth; |
1
2
3
4
5
|
//触发重绘 // setTimeout(()=>{ // dom.style.transition="transform .3s"; // dom.style.transform=``; // },0) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
clearTimeout(dom.animated); dom.animated=setTimeout(()=>{ dom.style.transition=""; dom.style.transform=``; dom.animated=false; },300) }, onDragEnd(event){ console.log('drag end') let currentNodes=Array.from(this.$refs.parentNode.childNodes); let data=currentNodes.map((i,index)=>{ let item=this.data.find(c=>c==i.innerText); return item }); console.log(data) }, _index(el){ let domData=Array.from(this.$refs.parentNode.childNodes); return domData.findIndex(i=>i.innerText==el.innerText); } } }) </ script > </ html > |
3.2.第二种实现
mousedown的时候记录下拖拽项和拖拽项初始位置,mouseover的时候将拖拽项和目标项交换位置,添加transform,mouseup的时候遍历出新数组来更新视图。这种方式就是动画不好加,个人瞎琢磨的,应该是思路错误了,放着看看吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
<! DOCTYPE html> < html > < head > < meta charset="utf-8"> < meta name="viewport" content="width=device-width,initial-scale=1.0"> < script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></ script > < style > ul{ list-style:none; padding-bottom:20px; } .item{ cursor: pointer; height:24px; line-height:24px;
border:1px solid #d9d9d9; border-radius:4px; color:#fff; padding:10px; user-select: none; } </ style > </ head > < body > < div id="app"> < ul ref="parentNode" @mouseover="onMouseOver" @mouseup="onMouseUp"> < li ref="li" v-for="(item,index) in data" :key="index" class="item" @mouseDown="(event)=>{onMouseDown(event,index)}" >{{item}}</ li > </ ul > </ div > </ body > < script > var app = new Vue({ el: '#app', data: { data:[1,2,3,4,5,6], isDonw:false, draging:null, dragStartPos:0 }, mounted () { //为了防止火狐浏览器拖拽的时候以新标签打开,此代码真实有效 document.body.ondrop = function (event) { event.preventDefault(); event.stopPropagation(); } document.onmouseup=()=>{ if(this.isDonw) this.onMouseUp() }; }, computed:{ nodes(){ return Array.from(this.$refs.parentNode.children) }, itemHeight(){ return this.nodes[0].offsetHeight; } }, methods:{ onMouseDown(event,index){ this.isDonw=true; this.draging=this.$refs['li'][index]; this.dragStartPos=this.draging.getBoundingClientRect().top; }, onMouseOver(event){ if(this.isDonw){ let target=event.target; let drag=this.draging; let Index=this._index(target); if(target.nodeName!='UL' && target!=drag){ let targetTop=target.getBoundingClientRect().top; let dragTop=drag.getBoundingClientRect().top; let targetOffset=targetTop-dragTop; let dragOffset=targetTop-this.dragStartPos; //样式变化 let targetStyle= target.style.transform; let lastTransform=0; if(targetStyle){ lastTransform=this.getTransform(targetStyle); } drag.style.transform=`translateY(${dragOffset}px)`; target.style.transform=`translateY(${lastTransform-targetOffset}px)`; } } }, onMouseUp(){ this.isDonw=false; this.draging=null; this.dragStartPos=0; let res=[] for(let i=0;i< this.nodes.length ;i++){ let item=this.nodes[i]; let transform=this.getTransform(item.style.transform); if(transform){ res[i+transform/this.itemHeight]=this.data[i]; }else{ res[i]=this.data[i]; } item.style.transform=''; item.style.transition=''; } this.data=[...res]; console.log(res) }, getTransform(style){ if(style){ let firstIndex=style.indexOf('(')+1; let lastIndex=style.indexOf(')')-2; return parseInt(style.substring(firstIndex,lastIndex)) } }, _index(el){ let domData=Array.from(this.$refs.parentNode.childNodes); return domData.findIndex(i=>i.innerText==el.innerText); } } }) </ script > </ html > |
引自:https://www.cnblogs.com/scdisplay/p/10431548.html