• 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>
    

      

  • 相关阅读:
    IHttpActionResult – new way of creating responses in ASP.NET Web API 2
    Web Api 中返回JSON的正确做法
    MVC中使用RazorPDF创建PDF
    Mac上javaweb开发环境搭建介绍----git安装及使用
    Web开发环境的搭建(mysql、maven)
    父pom.xml的详解
    pom.xml中的常用依赖包总结
    Maven+Spring+SpringMVC+Mybatis中的常见错误
    jqGrid中关于工具条中的输入框中内容的读取
    jqGrid的Fomatter用于将图片url地址转换成前端图片显示的使用
  • 原文地址:https://www.cnblogs.com/scdisplay/p/10431548.html
Copyright © 2020-2023  润新知