vue列表拖拽排序功能實現

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; 
          dom.style.transition="transform .3s";
          dom.style.transform=``;
                //觸發重繪
                // 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>
相關文章
相關標籤/搜索