vue可拖拽排序列表组件

24 篇文章 0 订阅
8 篇文章 0 订阅

先看效果:

dragable-lists.vue组件源码:

<template>
  <div class="drag-lists" ref="drag-lists">
    <slot />
    <!-- 占位dom -->
    <div class="perch" ref="perchRef"></div>
  </div>
</template>

<script>
export default {
  props: {
    direction: {
      default: 'y' // 可选值x:横向, y:纵向
    }
  },
  data() {
    return {
      isDrag: false, // 是否拖拽
      index: '', // 拖拽的列表index
      perchEl: null, // 占位div的dom
      parentEl: null, // 列表父dom
      listsEl: null, // 列表dom集合
      pointerPosition: {
        left: '',
        top: ''
      }, // 鼠标点击的点相对于当前dom的左和上偏移量
      dragListOldStyle: '', // list的原始style
      // 被拖拽div的宽高
      dragListElBaseProp: {
        width: '',
        height: ''
      }
    }
  },
  computed: {
    // 被拖拽dom
    dragListEl() {
      return this.listsEl[this.index]
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      document.addEventListener('mousedown', this.mouseDown)
      document.addEventListener('mousemove', this.mouseMove)
      document.addEventListener('mouseup', this.mouseUp)
      this.parentEl = this.$refs['drag-lists']
      this.listsEl = this.$slots.default.map((i) => i.elm) // 获取到slot插槽列表dom集合
      this.perchEl = this.$refs['perchRef']
      // 每个list加mouseenter事件(排序的逻辑)
      this.listsEl.forEach((el) => {
        el.addEventListener('mouseenter', () => {
          if (this.isDrag) {
            const lists = [...this.parentEl.children]
            const imgIndex = lists.findIndex((item) => el === item)
            const perchIndex = lists.findIndex((item) => this.perchEl === item)
            if (perchIndex < imgIndex) {
              this.parentEl.insertBefore(el, this.perchEl)
            } else {
              this.parentEl.insertBefore(this.perchEl, el)
            }
          }
        })
      })
    },
    mouseDown(e) {
      this.isDrag = true
      const { pageX: X, pageY: Y } = e
      this.index = e.target.dataset.index
      if (this.index && this.isDrag) {
        this.getDragListBaseProp()
        this.dragListOldStyle = this.dragListEl.getAttribute('style') || '' // 记录下被拖拽dom的style样式,方便mouseup时恢复

        // 设置占位dom的影响宽高的定位的样式和被拖拽dom的保持一致
        const { width, height, offsetLeft, offsetTop, margin, padding } = this.dragListElBaseProp
        this.perchEl.style.width = width + 'px'
        this.perchEl.style.height = height + 'px'
        this.perchEl.style.margin = margin
        this.perchEl.style.padding = padding
        this.perchEl.style.display = 'block' // 占位div显示

        this.parentEl.insertBefore(this.perchEl, this.dragListEl) // 在拖拽div前插入占位div

        this.pointerPosition = {
          left: X - offsetLeft,
          top: Y - offsetTop
        }
        this.move(this.dragListEl, X - this.pointerPosition.left, Y - this.pointerPosition.top) // 移动被拖拽的div
      }
    },
    mouseMove(e) {
      const { pageX: X, pageY: Y } = e
      if (this.index && this.isDrag) {
        this.move(this.dragListEl, X - this.pointerPosition.left, Y - this.pointerPosition.top) // 移动被拖拽的div
      }
    },
    mouseUp() {
      this.isDrag = false
      this.perchEl.style.display = 'none'
      if (this.dragListEl) {
        if (this.dragListOldStyle) {
          this.dragListEl.setAttribute('style', this.dragListOldStyle)
        } else {
          this.dragListEl.removeAttribute('style')
        }
        this.parentEl.insertBefore(this.dragListEl, this.perchEl)
      }
    },
    // 移动函数
    move(el, x, y) {
      // 如果移动模式是横向,则垂直方向偏移量固定为0
      if (this.direction === 'x') {
        y = 0
      }
      // 如果移动模式是纵向,则水平方向偏移量固定为0
      if (this.direction === 'y') {
        x = 0
      }
      // 设置宽高是position为absolute时,防止flex等布局下宽高获取不到的问题
      const { width, height } = this.dragListElBaseProp
      el.setAttribute(
        'style',
        `position: absolute;
          width: ${width}px;
          height: ${height}px;
          left: ${x}px;
          top: ${y}px;
          pointer-events: none;
          border-radius: 5px;
          background: #999;
          opacity: 0.7;
          ${this.dragListOldStyle}`
      )
    },
    // 获取被拖拽dom在拖拽前的宽高,左上偏移量,margin,padding
    getDragListBaseProp() {
      const dom = this.dragListEl
      const margin = dom.currentStyle ? dom.currentStyle['margin'] : getComputedStyle(dom, null)['margin']
      const padding = dom.currentStyle ? dom.currentStyle['padding'] : getComputedStyle(dom, null)['padding']
      const { width, height } = dom.getBoundingClientRect()
      const { offsetLeft, offsetTop } = dom
      this.dragListElBaseProp = { width, height, offsetLeft, offsetTop, margin, padding }
    }
  }
}
</script>
<style lang="scss" scoped>
.drag-lists {
  position: relative;
  .perch {
    display: none;
  }
}
</style>

使用方法:

<template>
  <div class="main">
    <dragableLists direction="x" style="display: flex">
      <div class="list" :data-index="index" v-for="(item, index) in lists" :key="index">{{ item.name }}</div>
    </dragableLists>
  </div>
</template>

<script>
import dragableLists from '@/components/dragable-lists.vue'
export default {
  components: { dragableLists },
  data() {
    return {
      lists: [{ name: '第一行' }, { name: '第二行第二行第二行第二行第二行' }, { name: '第三行' }]
    }
  }
}
</script>
<style lang="scss" scoped>
.main {
  width: 800px;
}
.list {
  padding: 20px;
  margin: 20px;
  background: #eee;
  flex: 1;
  text-align: center;
}
</style>

---------------

如果是移动端,需要把mousedown、mouseUp替换为touchstart、touchend,mouseenter、mousemove替换为touchmove,并且事件的参数e要取值e.touches[0]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值