1.實現目標:目標是輸入一個數組,生成一個列表;經過拖拽排序,拖拽結束後輸出一個通過排序的數組。html
2.實現思路:vue
2.1是使用HTML5的drag功能來實現,每次拖拽時直接操做Dom節點排序,拖拽結束後再根據實際的dom節點遍歷得出新的數組。node
2.2使用mousedown,mouseover等鼠標事件來實現,每次監聽事件時,僅改動列表項的樣式transform,而不操做實際的dom順序。拖拽結束時,根據transform計算數組項順序,得出新數組用vue數據驅動的方式重繪列表,重置全部樣式。npm
總的來講就是能夠經過不一樣的監聽事件(drag、mouseover),按不一樣的順序操做Dom(1.先操做實際dom,再添加動畫,在輸出數組;2。不操做實際dom,僅改變transfrom,得出新數組,用新數組生成新列表來更新節點)。數組
3.實際代碼瀏覽器
3.1第一種實現app
html部分。(被拖拽的元素須要設置draggable=true,不然不會有效果)dom
<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元素的前後順序。this
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的偏移量能夠經過改變節點順序先後的距頂高度來得到。
完整代碼:
<!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; background-color:#9c9c9c; 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;
//觸發重繪 // setTimeout(()=>{ // dom.style.transition="transform .3s"; // dom.style.transform=``; // },0)
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的時候遍歷出新數組來更新視圖。這種方式就是動畫很差加,我的瞎琢磨的,應該是思路錯誤了,放着看看吧。
<!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; background-color:#9c9c9c; 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>