vue列表拖拽排序功能实现

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,否则不会有效果)

<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元素的先后顺序。

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>

  

猜你喜欢

转载自www.cnblogs.com/scdisplay/p/10431548.html