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