先看效果:
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]