vue实现可拖拽列表组件

14 篇文章 0 订阅
7 篇文章 0 订阅

列表内容和样式在外部自定义,话不多说,直接上代码

1. 用drag事件实现,唯一不好的是拖拽时的半透明框没法修改

<template>
  <div class="list" ref="listRef" @dragstart="drag" @dragover="dragover" @dragend="dragend" @mouseout="out">
    <div class="list-item" v-for="(item, index) in listData" :key="index" :orgIndex="index" :draggable="isDrag"
      @click="click">
      <slot :row="item"></slot>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'DragList',
    props: {
      listData: Array, // list绑定的数据
      isDrag: Boolean, // 是否开启拖拽
      callDrop: Function,
      callClick: Function
    },
    data() {
      return {
        oldListInfo: [],  // 拖拽前数据
        listInfo: [],     // 拖拽后数据
        draging: null,    // 被拖拽元素
        target: null,     // 目标元素
        allow: false      // 是否可拖拽,鼠标按下后才可拖拽
      }
    },
    created() {
      this.listInfo = this.listData;
    },
    methods: {
      click(e) {
        let listItem = this.getParentNode(e.target, "list-item");
        if (!listItem) return;
        let clickItemIndex = this.getIndex(listItem);
        // call 自定义click,返回被点击数据
        if (this.callClick && typeof (this.callClick) == 'function') {
          this.callClick({"index": clickItemIndex, "item": this.listInfo[clickItemIndex]});
        }
      },
      drag(e) {
        this.draging = e.target;
        this.allow = true;
        this.oldListInfo = this.listInfo;
      },
      dragover(e) {
        e.preventDefault();
        if (!this.allow) return;
        let listItem = this.getParentNode(e.target, "list-item");
        if (!listItem) return;

        this.target = listItem;

        let targetTop = this.target.getBoundingClientRect().top;
        let dragingTop = this.draging.getBoundingClientRect().top;

        let dragingIndex = this.getIndex(this.draging);
        let targetIndex = this.getIndex(this.target);

        if (this.target.className === "list-item" && this.target !== this.draging) {
          if (this.target) {
            if (this.target.animated) {
              return;
            }
          }
          // 互换位置
          if (dragingIndex < targetIndex) {
            this.target.parentNode.insertBefore(this.draging, this.target.nextSibling);
          } else {
            this.target.parentNode.insertBefore(this.draging, this.target);
          }
          this.animate(targetTop, this.target);
          this.animate(dragingTop, this.draging);
        }
      },
      dragend(e) {
        // 返回调整后的数据,调用外部自定义函数
        this.listInfo = this.getNewData();

        if (this.callDrop && typeof (this.callDrop) == 'function') {
          this.callDrop({"oldListInfo": this.oldListInfo, "newListInfo": this.listInfo});
        }
      },
      // 鼠标移出list
      out(e) {
        if (e.clientY < this.$refs.listRef.getBoundingClientRect().top || e.clientY > this.$refs.listRef
          .getBoundingClientRect().bottom) {
          this.allow = false;
        }
      },
      /*
       * 按类名查询父元素
       * node:当前节点
       * pclassname:父元素类名
       */
      getParentNode(node, pclassname) {
        let p = node;
        if (p.className == pclassname) return p;
        while (p.parentNode) {
          if (p.parentNode.className == pclassname) {
            return p.parentNode;
          } else {
            p = p.parentNode;
          }
          if (p.nodeName == "BODY") return 0;
        }
      },
      /*
       * 获取元素在父元素的索引
       * node:当前节点
       * pclassname:父元素类名
       */
      getIndex(el) {
        let domData = Array.from(this.$refs.listRef.childNodes);
        return domData.findIndex(i => i.innerText == el.innerText);
      },
      // 动画效果
      animate(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 = "";
        clearTimeout(dom.animated);

        dom.animated = setTimeout(() => {
          dom.style.transition = "";
          dom.style.transform = "";
          dom.animated = false;
        }, 300)
      },
      // 获取拖拽后的数据
      getNewData() {
        let currentNodes = Array.from(this.$refs.listRef.childNodes);
        let listArr = [];
        currentNodes.map((i, index) => {
          return listArr[index] = this.listData[i.getAttribute("orgIndex")];
        });
        return listArr;
      }
    }
  }

</script>

<style scoped>
  .list {
    display: block;
  }
  .list-item {
    width: 100%;
    height: 100%;
    user-select: none;
    cursor: pointer;
  }
</style>

2. mouse事件实现

<template>
  <div class="list" ref="listRef1" @mousedown="drag" @mousemove='dragover' @mouseup='dragend' @mouseout="out">
    <div class="list-item" v-for="(item, index) in listData" :key="index" :orgIndex="index"
      @click="click">
      <slot :row="item"></slot>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'DragList1',
    props: {
      listData: Array, // list绑定的数据
      isDrag: Boolean, // 是否开启拖拽
      callDrop: Function,
      callClick: Function
    },
    data() {
      return {
        oldListInfo: [],  // 拖拽前数据
        listInfo: [],     // 拖拽后数据
        draging: null,    // 被拖拽元素
        target: null,     // 目标元素
        allow: false,     // 是否可拖拽,鼠标按下后才可拖拽
        isMove: false     // 是否移动,区分click
      }
    },
    created() {
      this.listInfo = this.listData;
    },
    methods: {
      click(e) {
        // 如果是移动状态
        if (this.isMove) return;
        let listItem = this.getParentNode(e.target, "list-item");
        if (!listItem) return;
        let clickItemIndex = this.getIndex(listItem);
        // call 自定义click,返回被点击数据
        if (this.callClick && typeof (this.callClick) == 'function') {
          this.callClick({"index": clickItemIndex, "item": this.listInfo[clickItemIndex]});
        }
      },
      drag(e) {
        this.isMove = false;    // 鼠标按下未移动
        // 如果未开启拖拽
        if (!this.isDrag) return;
        // 获取当前点击的item
        let listItem = this.getParentNode(e.target, "list-item");
        if (!listItem) return;
        this.draging = listItem;

        this.allow = true;  // 允许拖拽
        this.oldListInfo = this.listInfo; // 当前数据变为旧数据
      },
      dragover(e) {
        // 未开启或者不允许时,不触发
        if (!this.isDrag || !this.allow)  return;
        this.isMove = true;       // 鼠标按下移动
        // 获取移动到的item
        let listItem = this.getParentNode(e.target, "list-item");
        if (!listItem) return;

        this.target = listItem;
        // 获取两个位置的纵坐标
        let targetTop = this.target.getBoundingClientRect().top;
        let dragingTop = this.draging.getBoundingClientRect().top;

        let dragingIndex = this.getIndex(this.draging);
        let targetIndex = this.getIndex(this.target);

        if (this.target.className === "list-item" && this.target !== this.draging) {
          if (this.target) {
            // 如果正在进行动画
            if (this.target.animated) {
              return;
            }
          }
          if (dragingIndex < targetIndex) {
            this.target.parentNode.insertBefore(this.draging, this.target.nextSibling);
          } else {
            this.target.parentNode.insertBefore(this.draging, this.target);
          }
          this.animate(dragingTop, this.draging);
          this.animate(targetTop, this.target);
        }
      },
      dragend(e) {
        this.allow = false;   // 重置状态

        if (!this.isDrag || !this.isMove) return;
        // 获取拖拽后数据
        this.listInfo = this.getNewData();
        // call自定义事件,返回新旧数据
        if (this.callDrop && typeof (this.callDrop) == 'function') {
          this.callDrop({"oldListInfo": this.oldListInfo, "newListInfo": this.listInfo});
        }
        this.isMove = false;  // 重置状态
      },
      out(e) {
        if (!this.allow)  return;
        e = e || window.event; 
        var obj = e.toElement || e.relatedTarget;
        // 鼠标移出list范围
        if (!this.$refs.listRef1.contains(obj)) {
          this.allow = false;
          this.listInfo = this.getNewData();
          if (this.callDrop && typeof (this.callDrop) == 'function') {
            this.callDrop({"oldListInfo": this.oldListInfo, "newListInfo": this.listInfo});
          }
          this.isMove = false;
        }
      },
      /*
       * 按类名查询父元素
       * node:当前节点
       * pclassname:父元素类名
       */
      getParentNode(node, pclassname) {
        let p = node;
        while (p.parentNode) {
          if (p.parentNode.className == pclassname) {
            return p.parentNode;
          } else {
            p = p.parentNode;
          }
          if (p.nodeName == "BODY") return 0;
        }
      },
      /*
       * 获取元素在父元素的索引
       * node:当前节点
       * pclassname:父元素类名
       */
      getIndex(el) {
        let domData = Array.from(this.$refs.listRef1.childNodes);
        return domData.findIndex(i => i.innerText == el.innerText);
      },
      // 动画效果
      animate(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 = "";
        clearTimeout(dom.animated);

        dom.animated = setTimeout(() => {
          dom.style.transition = "";
          dom.style.transform = "";
          dom.animated = false;
        }, 300)
      },
      // 获取拖拽后的数据
      getNewData() {
        let currentNodes = Array.from(this.$refs.listRef1.childNodes);
        let listArr = [];
        currentNodes.map((i, index) => {
          return listArr[index] = this.listData[i.getAttribute("orgIndex")];
        });
        return listArr;
      }
    }
  }

</script>

<style scoped>
  .list {
    display: block;
  }
  .list-item {
    width: 100%;
    height: 100%;
    user-select: none;
    cursor: pointer;
  }

</style>

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值