uniapp 小程序端简单实现一个拖动排序
在小程序端实现这个功能没有 web 端那么方便,在插件市场也没有找到合适的,所以简单实现一个。
我们知道,一般来说要实现拖动排序的列表大概率没有很长很长,所以就不做长列表的优化了。
微信小程序可以单独使用 wxs 优化
当然实现方式多样,适合自己的才是最好的。
简单的部分原理说明
通常来说,部分列表项拥有自己特定的响应事件,例如点击操作,我们实现拖动最好和这些事件避开。
首先确定列表项的排列方式,我们选择的是定位来布局列表项,因为这样方便操作每个元素的位置,直接动态改变元素的定位信息即可。
接着就是怎么确定元素的位置信息,包括后面如何判断拖动元素位于某个元素内部,基于这些,我们就必须知道列表项的宽高信息,基于列表项的宽高,就可以很好计算这些信息。所以,得定宽高!
宽高有了就不分析了~~~
实现
<template>
<view>
<view
class="test-list"
@touchstart="handleStart"
@touchmove="handleMove"
@touchend="handleEnd"
:style="{
overflowY: currItem ? 'hidden' : 'scroll'
}"
>
<view
class="test-item test-item-base"
v-for="item in dataList"
:key="item.id"
@longpress="handleLongpress(item)"
:style="{
height: height + 'px',
width: width + 'px',
opacity: item.id === currItem.id ? 0.4 : 1,
top: item.y + 'px',
left: item.x + 'px'
}"
>
<text>{{ item.title }}</text>
</view>
<view
class="test-item"
v-if="currItem"
:style="{
height: height + 'px',
width: width + 'px',
top: currItem.y + 'px',
left: currItem.x + 'px'
}"
>
<text>{{ currItem.title }}</text>
</view>
</view>
</view>
</template>
<script>
const BASE_MARGIN = uni.upx2px(30)
const BASE_MARGIN_TOP = uni.upx2px(20)
export default {
data() {
return {
height: uni.upx2px(400),
width: uni.upx2px(330),
windowHeight: 0,
windowWidth: 0,
dataList: [],
currItem: null,
startTouch: {
x: 0,
y: 0
},
sortLoading: false
}
},
methods: {
// 手指按下,记录初始位置信息,用于计算当前元素移动距离
handleStart(e) {
let { clientX: x, clientY: y } = e.touches[0]
this.startTouch = { x, y }
},
// 手指拖动排序
handleMove(e) {
if (!this.currItem) return
let { clientX: x, clientY: y } = e.touches[0]
this.currItem.x += x - this.startTouch.x
this.currItem.y += y - this.startTouch.y
let tIndex = this.dataList.findIndex(o => o.id == this.currItem.id)
if (tIndex > -1) {
// 判断拖动中的元素位于哪个元素内部
let i = this.dataList.findIndex(item => {
// 可优化
return (
// 当前元素左上角位于元素内部
this.currItem.x <= (item.x + this.width / 2) &&
this.currItem.y <= (item.y + this.height / 2) &&
this.currItem.y >= item.y &&
this.currItem.x >= item.x
) || (
// 当前元素左下角位于元素内部
this.currItem.x >= item.x &&
this.currItem.x <= (item.x + this.width / 2) &&
(this.currItem.y + this.height) >= (item.y + this.height / 2) &&
(this.currItem.y + this.height) <= (item.y + this.height)
) || (
// 当前元素右上角位于元素内部
this.currItem.y >= item.y &&
this.currItem.y <= (item.y + this.height / 2) &&
(this.currItem.x + this.width) >= (item.x + this.width / 2) &&
(this.currItem.x + this.width) <= (item.x + this.width)
) || (
// 当前元素右下角位于元素内部
(this.currItem.y + this.height) >= (item.y + this.height / 2) &&
(this.currItem.y + this.height) <= (item.y + this.height) &&
(this.currItem.x + this.width) >= (item.x + this.width / 2) &&
(this.currItem.x + this.width) <= (item.x + this.width)
)
})
if (i != tIndex && i > -1) {
let tItem = this.dataList[tIndex]
// 简单实现元素位置更改
this.dataList.splice(tIndex, 1)
this.dataList.splice(i, 0, tItem)
// 优化,改变位置的元素始终都是当前元素和被拖到的位置元素之间的元素
let maxI = i > tIndex ? i : tIndex
let minI = i > tIndex ? tIndex : i
while (minI <= maxI) {
let { px, py } = this.handlePosition(minI)
let item = this.dataList[minI]
item.x = px
item.y = py
// let _index = minI + 1
// let item = this.dataList[minI]
// if (_index % 2 === 0) {
// item.x = this.windowWidth - BASE_MARGIN - this.width
// } else {
// item.x = BASE_MARGIN
// }
// if (_index <= 2) {
// item.y = BASE_MARGIN_TOP
// } else {
// item.y = (Math.ceil(_index / 2) - 1) * (BASE_MARGIN_TOP + this.height) + BASE_MARGIN_TOP
// }
minI++
}
// this.dataList.forEach((item, index) => {
// let _index = index + 1
// if (_index % 2 === 0) {
// item.x = this.windowWidth - BASE_MARGIN - this.width
// } else {
// item.x = BASE_MARGIN
// }
// if (_index <= 2) {
// item.y = BASE_MARGIN_TOP
// } else {
// item.y = (Math.ceil(_index / 2) - 1) * (BASE_MARGIN_TOP + this.height) + BASE_MARGIN_TOP
// }
// })
}
this.startTouch = { x, y }
}
},
// 手指拖动结束,清空当前拖动的元素,开始进行排序确定
handleEnd() {
if (!this.currItem) return
this.currItem = null
this.handleSort()
},
// 模拟排序
handleSort() {
if (this.sortLoading) return
this.sortLoading = true
// 向后端排序接口发起请求
setTimeout(() => {
this.sortLoading = false
}, 100)
},
// 长按选中当前要进行拖动排序的元素
handleLongpress(item) {
if (this.currItem != null || this.sortLoading) return
this.currItem = JSON.parse(JSON.stringify(item))
this.currItem.x = this.currItem.x + 10
this.currItem.y = this.currItem.y + 10
},
// 计算元素位置信息,通过当前元素的索引和初始化宽高来计算
handlePosition(i) {
let px = 0
let py = 0
let _index = i + 1
if (_index % 2 === 0) {
px = this.windowWidth - BASE_MARGIN - this.width
} else {
px = BASE_MARGIN
}
if (_index <= 2) {
py = BASE_MARGIN_TOP
} else {
py = (Math.ceil(_index / 2) - 1) * (BASE_MARGIN_TOP + this.height) + BASE_MARGIN_TOP
}
return { px, py }
}
},
created() {
let systemInfo = uni.getSystemInfoSync()
this.windowHeight = systemInfo.windowHeight
this.windowWidth = systemInfo.windowWidth
// 模拟获取到数据并对数据进行处理
for (let i = 0; i < 10; i++) {
// let x = 0
// let y = 0
// let _index = i + 1
// if (_index % 2 === 0) {
// x = this.windowWidth - BASE_MARGIN - this.width
// } else {
// x = BASE_MARGIN
// }
// if (_index <= 2) {
// y = BASE_MARGIN_TOP
// } else {
// y = (Math.ceil(_index / 2) - 1) * (BASE_MARGIN_TOP + this.height) + BASE_MARGIN_TOP
// }
let { px, py } = this.handlePosition(i)
this.dataList.push({ id: i, title: `测试${i}`, x: px, y: py })
}
}
}
</script>
<style scoped lang="scss">
.test-list {
position: relative;
height: 100vh;
}
.test-item {
position: absolute;
background-color: orange;
}
.test-item-base {
transition: all .5s;
}
</style>