一、拖拽效果图展示
首先,上个gif图看看效果
吐血测试了一天,目前还未发现bug。ps(拖拽效果仅在前端实现,未和后端交互)
文章代码参考小程序实现列表拖拽排序 ,参考文章还是存在一些bug和不足,比如,样式代码没给对,还有实现的拖拽是拖拽起始位置数据的替换,不是我想要的拖拽效果。
二、解决思路
首先,拖拽重要的计算清楚位置关系,从什么地方开始拖,拖到什么地方结束,以及如何判断有效的拖拽排序。
在拖拽开始时、拖拽过程中、拖拽结束时获取拖拽数据项的索引和实时的位置相关数据。给拖拽节点绑定catchtouchstart、catchtouchmove、catchtouchend事件,利用data-xxx属性传递索引)
<!-- 拖拽节点 -->
<view data-index='{{index}}' catchtouchstart='dragStart' catchtouchmove='dragMove'
catchtouchend='dragEnd'>
<image class="img" src="/image/icon-sort.png"></image>
</view>
结合下图进行分析,如图,需要拖拽的数据列表都放在class=habitlist的节点中,为方便计算位置,固定每个数据项的height=60px;假设数据列表habitList=[{ name: "跳舞",icon: "./image/icon/icon-dance.png"},{name:"看书",...},...],当前拖拽项(name为跳舞的项)的克隆为kelong={name: "跳舞",icon: "./image/icon/icon-dance.png"}
1、【catchtouchstart】-拖拽开始时,获取当前拖拽项的索引,克隆当前项的内容给对象kelong。通过节点查询器,选择class=habitlist的节点,获取节点位置信息的查询请求,rect.top为该节点距离顶部的距离;rec.height为该节点区域的高度;e.changedTouches[0].clientY代表当前点击位置距离可视区域顶部的垂直距离,由于每个数据项的height=60px;预估:top=.changedTouches[0].clientY - rect.top - 30为当前克隆项显示的top,注意:克隆项显示的位置要相对class=habitlist的节点定位(子绝父相)。该值也为拖拽开始时的kelong项的位置startTop,(startTop值主要用于拖拽结束时判断是向下还是向上拖拽了)
// 拖拽开始
dragStart(e) {
// console.log("拖拽开始", e);
var i = e.currentTarget.dataset.index // 当前拖拽项的索引index
// 把当前拖拽项的内容复制给kelong
var kelong = this.data.habitList[i]
var query = wx.createSelectorQuery() // 创建节点查询器 quer
//选择class=habitlist的节点,获取节点位置信息的查询请求
query.select('.habitlist').boundingClientRect((rect) => {
var top = e.changedTouches[0].clientY - rect.top - 30
var startTop = top;
this.setData({
kelong: kelong,
selectedIndex:i,
showkelong: true,
top:top,
startTop:startTop
})
}).exec();
},
2、【catchtouchmove】-拖拽移动时,要对拖拽的范围做控制,文中做了顶部边界控制:控制克隆项不会拖拽出class=habitlist节点的顶部边界。当top<0时,将top置为0,底部可不做限制。
// 拖拽移动
dragMove(e) {
// console.log("拖拽移动", e);
var query = wx.createSelectorQuery();
var top =this.data.top
query.select('.habitlist').boundingClientRect((rect) => {
top = e.changedTouches[0].clientY - rect.top - 30
if (top < 0) {
// 顶部边界控制:控制克隆项不会拖拽出class=habitlist节点的顶部边界
top = 0
}
this.setData({
top:top
})
}).exec();
},
3、【catchtouchend】-拖拽结束时,
(1)进行边界控制:不仅要进行顶部边界限制,还要做底部边界控制,即如果拖拽超出底部边界,则该拖拽数据项为最后一个位置的数据了。
(2)向上或向下拖拽的数据备份及处理:向上拖拽时,需要备份插入位置target开始的下方数据(除了拖拽数据项);向下拖拽时,需要备份插入位置target开始的上方数据;然后按拖拽后的正确索引位置遍历赋值。
// 拖拽结束
dragEnd(e) {
// console.log("拖拽结束", e);
var i = e.currentTarget.dataset.index
var query = wx.createSelectorQuery();
var kelong = this.data.kelong
var habitList = this.data.habitList
query.select('.habitlist').boundingClientRect((rect) => {
var top = e.changedTouches[0].clientY - rect.top - 30
if (top > rect.height) {
// 底部边界控制:控制克隆项拖拽结束时不会出class=habitlist节点的底部边界
top = rect.height - 60
} else if (top < 0) {
// 顶部边界控制:控制克隆项拖拽结束时不会出class=habitlist节点的顶部边界
top = 0
}
this.setData({
top: top,
})
var target = parseInt(top / 60)
var list = [] //用于备份数据
if (this.data.startTop > top) {
// 往上方位置拖拽
for (var k = 0; k <= i - target; k++) {
// 备份插入位置target开始的下方数据,除了拖拽数据项
if (habitList[target + k].name != kelong.name) {
list.push(habitList[target + k])
}
}
if (list.lenghth != 0) {
habitList[target] = kelong
for (var m = target + 1, n = 0; n < list.length; m++, n++) {
habitList[m] = list[n]
}
}
} else {
// 往下边位置拖拽
for (var k = 1; k <= target - i; k++) {
// 备份插入位置target开始的上方数据,除了拖拽数据项
if (habitList[i + k].name != kelong.name) {
list.push(habitList[i + k])
}
}
if (list.length != 0) {
habitList[target] = kelong
for (var m = i, n = 0; n < list.length; m++, n++) {
habitList[m] = list[n]
}
}
}
this.setData({
habitList: habitList,
selectedIndex:-1,
showkelong: false
})
}).exec();
},
三、完整代码
【sortHabit.wxml】
<view class="page-wrapper">
<top-title toptitle="排序习惯" backImgFlag="true"></top-title>
<view class="habitlist">
<!-- 克隆当前拖拽的项 -->
<view class='habit kelong' hidden='{{!showkelong}}' style='top:{{top}}px'>
<view class='index'>?</view>
<view class="icon">
<image class="iconImg" src="{{kelong.icon}}"></image>
</view>
<view class="info">
<view class="title">{{kelong.name}}</view>
</view>
<image class="img"
src="cloud://xbd-cloud-xxx/icon/icon-sort.png"></image>
</view>
<block wx:for="{{habitList}}" wx:key="name">
<view class="habit {{selectedIndex==index?'gray':''}}" >
<view class='index'>{{index+1}}</view>
<view class="icon">
<image class="iconImg" src="{{item.icon}}"></image>
</view>
<view class="info">
<view class="title">{{item.name}}</view>
</view>
<!-- 拖拽节点 -->
<view data-index='{{index}}' catchtouchstart='dragStart' catchtouchmove='dragMove'
catchtouchend='dragEnd'>
<image class="img"
src="cloud://xbd-cloud-xxx/icon/icon-sort.png"></image>
</view>
</view>
</block>
</view>
</view>
【sortHabit.js】
Page({
/**
* 页面的初始数据
*/
data: {
habitList: [],
// 当前拖拽项的克隆
kelong: {
name: '',
icon: ''
},
startTop: 0, //拖拽开始时克隆项距离class=habitlist节点顶部边界的值
top: 0,
selectedIndex: -1, //被选择拖拽的项的index
backupList: [], //用于备份数据
showkelong: false, //是否显示克隆项
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var list = [{
name: "跳舞",
icon: "cloud://xbd-cloud-xxx/icon/icon-dance.png"
}, {
name: "看书",
icon: "cloud://xbd-cloud-xxx/icon/icon-reading.png"
}, {
name: "运动",
icon: "cloud://xbd-cloud-xxx/icon/icon-sports.png"
}, {
name: "护肤",
icon: "cloud://xbd-cloud-xxx/icon/icon-facialmask.png"
}]
this.setData({
habitList: list
})
},
// 拖拽开始
dragStart(e) {
// console.log("拖拽开始", e);
var i = e.currentTarget.dataset.index // 当前拖拽项的索引index
// 把当前拖拽项的内容复制给kelong
var kelong = this.data.habitList[i]
console.log("拖拽开始i=",i,"kelong=",kelong);
var query = wx.createSelectorQuery(); // 创建节点查询器 quer
//选择class=habitlist的节点,获取节点位置信息的查询请求
query.select('.habitlist').boundingClientRect((rect) => {
var top = e.changedTouches[0].clientY - rect.top - 30
var startTop = top;
this.setData({
kelong: kelong,
selectedIndex:i,
showkelong: true,
top:top,
startTop:startTop
})
}).exec();
},
// 拖拽移动
dragMove(e) {
// console.log("拖拽移动", e);
var query = wx.createSelectorQuery();
var top =this.data.top
query.select('.habitlist').boundingClientRect((rect) => {
top = e.changedTouches[0].clientY - rect.top - 30
if (top < 0) {
// 顶部边界控制:控制克隆项不会拖拽出class=habitlist节点的顶部边界
top = 0
}
this.setData({
top:top
})
}).exec();
},
// 拖拽结束
dragEnd(e) {
// console.log("拖拽结束", e);
var i = e.currentTarget.dataset.index
var query = wx.createSelectorQuery();
var kelong = this.data.kelong
var habitList = this.data.habitList
query.select('.habitlist').boundingClientRect((rect) => {
var top = e.changedTouches[0].clientY - rect.top - 30
if (top > rect.height) {
// 底部边界控制:控制克隆项拖拽结束时不会出class=habitlist节点的底部边界
top = rect.height - 60
} else if (top < 0) {
// 顶部边界控制:控制克隆项拖拽结束时不会出class=habitlist节点的顶部边界
top = 0
}
this.setData({
top: top,
})
var target = parseInt(top / 60)
var list = [] //用于备份数据
if (this.data.startTop > top) {
// 往上方位置拖拽
for (var k = 0; k <= i - target; k++) {
// 备份插入位置target开始的下方数据,除了拖拽数据项
if (habitList[target + k].name != kelong.name) {
list.push(habitList[target + k])
}
}
console.log("往上拖拽 list=======", list);
if (list.lenghth != 0) {
habitList[target] = kelong
for (var m = target + 1, n = 0; n < list.length; m++, n++) {
habitList[m] = list[n]
}
}
} else {
// 往下边位置拖拽
for (var k = 1; k <= target - i; k++) {
// 备份插入位置target开始的上方数据,除了拖拽数据项
if (habitList[i + k].name != kelong.name) {
list.push(habitList[i + k])
}
}
console.log("往下拖拽 list=======", list);
if (list.length != 0) {
habitList[target] = kelong
for (var m = i, n = 0; n < list.length; m++, n++) {
habitList[m] = list[n]
}
}
}
console.log(habitList);
this.setData({
habitList: habitList,
selectedIndex:-1,
showkelong: false
})
}).exec();
},
})
【sortHabit.wxss】
.habit {
margin: 0rpx auto;
width: 100%;
height: 60px;
border-radius: 10rpx;
background-color: #fff;
display: flex;
padding: 15rpx;
box-sizing: border-box;
position: relative;
border-bottom: 1px solid #f1f1f1;
}
.habit .index {
margin: 25rpx 20rpx 0px 30rpx;
font-weight: bold;
color: #c93a3a;
}
.habit .icon {
margin-right: 25rpx;
}
.habit .icon .iconImg {
margin-top: 10rpx;
width: 70rpx;
height: 70rpx;
}
.habit .info {
display: flex;
flex-direction: column;
width: 60vw;
}
.habit .info .title {
margin: 25rpx 0rpx;
font-size: 30rpx;
}
.habit .img {
position: absolute;
right: 30rpx;
margin-top: 25rpx;
width: 40rpx;
height: 40rpx;
}
.habitlist {
position: relative;
}
.kelong {
z-index: 999;
position: absolute;
box-shadow: 1px 1px 5px #ccc;
}
.gray {
background-color: #efecec;
}
ps:也不知道自己讲清楚没,记录点滴心路历程~下篇讲讲日历打卡