小程序
小游戏
企业微信
微信支付
扫描小程序码分享
拖拽排序,但是列表的每个元素的高度不一样,拖拽就有问题,大佬,救救孩子
2 个回答
加粗
标红
插入代码
插入链接
插入图片
上传视频
自己写的?还是框架?
你好,麻烦通过点击下方“反馈信息”按钮,提供出现问题的。
找轮子,不过一般都要根据自己的情况改的,基本思路就是使用movable-view来做,布局那块你要自己写函数生成布局的x和y,当拖动一个块到另一个块上触发touchend的时候对比一下x和y判断是在哪个块上,然后触发位置交换,直接交换两个块的x和y就行。大概思路就这样,细节的慢慢抠一般能写出来。
关注后,可在微信内接收相应的重要提醒。
请使用微信扫描二维码关注 “微信开放社区” 公众号
自己写的?还是框架?
/**
* 判断是否超出范围
*/
const IsOutRange = (x1, y1, x2, y2, x3, y3) => {
return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
};
/**
* 版本号比较
*/
const compareVersion = (v1, v2) => {
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while (v1.length < len) {
v1.push('0')
}
while (v2.length < len) {
v2.push('0')
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i])
const num2 = parseInt(v2[i])
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
}
Component({
options: {
multipleSlots: true
},
properties: {
extraNodes: {type: Array, value: []}, // 额外节点
listData: {type: Array, value: []}, // 数据源
columns: {type: Number, value: 1}, // 列数
topSize: {type: Number, value: 0}, // 顶部高度
bottomSize: {type: Number, value: 0}, // 底部高度
scrollTop: {type: Number, value: 0} // 页面滚动高度
},
data: {
/* 未渲染数据 */
pageMetaSupport: false, // 当前版本是否支持 page-meta 标签
windowHeight: 0, // 视窗高度
platform: '', // 平台信息
realTopSize: 0, // 计算后顶部固定高度实际值
realBottomSize: 0, // 计算后底部固定高度实际值
rows: 0, // 行数
itemDom: {width: 0, height: 0, left: 0, top: 0}, // 每一项 item 的 dom 信息, 由于大小一样所以只存储一个
itemWrapDom: {width: 0, height: 0, left: 0, top: 0}, // 整个拖拽区域的 dom 信息
startId: 0, // 初始触摸点 identifier
preStartKey: -1, // 前一次排序时候的起始 sortKey 值
/* 渲染数据 */
list: [], // 渲染数据列
cur: -1, // 当前激活的元素
curZ: -1, // 当前激活的元素, 用于控制激活元素z轴显示
tranX: 0, // 当前激活元素的 X轴 偏移量
tranY: 0, // 当前激活元素的 Y轴 偏移量
itemWrapHeight: 0, // 动态计算父级元素高度
dragging: false, // 是否在拖拽中
itemTransition: false, // item 变换是否需要过渡动画, 首次渲染不需要
putShow:false,
show:false
},
methods: {
openClick(){
this.setData({putShow: true});
},
putClick(){
this.setData({putShow: false});
},
delTeammate(){
this.setData({
show: true
});
},
updateTeammate(){
},
onClose() {
this.setData({
show: false
});
},
/**
* 长按触发移动排序
*/
longPress(e) {
// 获取触摸点信息
let startTouch = e.changedTouches[0];
if (!startTouch) return;
// 固定项则返回
let index = e.currentTarget.dataset.index;
if (this.isFixed(index)) return;
// 防止多指触发 drag 动作, 如果已经在 drag 中则返回, touchstart 事件中有效果
if (this.data.dragging) return;
this.setData({dragging: true});
let {platform, itemDom, itemWrapDom} = this.data,
{pageX: startPageX, pageY: startPageY, identifier: startId} = startTouch;
// 计算X,Y轴初始位移, 使 item 中心移动到点击处
let tranX = startPageX - itemDom.width / 2 - itemWrapDom.left,
tranY = startPageY - itemDom.height / 2 - itemWrapDom.top;
// 单列时候X轴初始不做位移
if (this.data.columns === 1) tranX = 0;
this.data.startId = startId;
this.setData({cur: index, curZ: index, tranX, tranY});
if (platform !== "devtools") wx.vibrateShort();
},
touchMove(e) {
// 获取触摸点信息
let currentTouch = e.changedTouches[0];
if (!currentTouch) return;
if (!this.data.dragging) return;
let {pageMetaSupport, windowHeight, realTopSize, realBottomSize, itemDom, itemWrapDom, preStartKey, columns, rows} = this.data,
{pageX: currentPageX, pageY: currentPageY, identifier: currentId, clientY: currentClientY} = currentTouch;
// 如果不是同一个触发点则返回
if (this.data.startId !== currentId) return;
// 通过 当前坐标点, 初始坐标点, 初始偏移量 来计算当前偏移量
let tranX = currentPageX - itemDom.width / 2 - itemWrapDom.left,
tranY = currentPageY - itemDom.height / 2 - itemWrapDom.top;
// 单列时候X轴初始不做位移
if (columns === 1) tranX = 0;
// 到顶到底自动滑动
if (currentClientY > windowHeight - itemDom.height - realBottomSize) {
// 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度)
if (pageMetaSupport) {
this.triggerEvent("scroll", {
scrollTop: currentPageY + itemDom.height - (windowHeight - realBottomSize)
});
} else {
wx.pageScrollTo({
scrollTop: currentPageY + itemDom.height - (windowHeight - realBottomSize),
duration: 300
});
}
} else if (currentClientY < itemDom.height + realTopSize) {
// 当前触摸点pageY - item高度 - 顶部固定区域高度
if (pageMetaSupport) {
this.triggerEvent("scroll", {
scrollTop: currentPageY - itemDom.height - realTopSize
});
} else {
wx.pageScrollTo({
scrollTop: currentPageY - itemDom.height - realTopSize,
duration: 300
});
}
}
// 设置当前激活元素偏移量
this.setData({tranX: tranX, tranY: tranY});
// 获取 startKey 和 endKey
let startKey = parseInt(e.currentTarget.dataset.key);
let curX = Math.round(tranX / itemDom.width), curY = Math.round(tranY / itemDom.height);
let endKey = curX + columns * curY;
// 遇到固定项和超出范围则返回
if (this.isFixed(endKey) || IsOutRange(curX, columns, curY, rows, endKey, this.data.list.length)) return;
// 防止拖拽过程中发生乱序问题
if (startKey === endKey || startKey === preStartKey) return;
this.data.preStartKey = startKey;
// 触发排序
this.sort(startKey, endKey);
},
touchEnd() {
if (!this.data.dragging) return;
this.triggerCustomEvent(this.data.list, "sortend");
this.clearData();
},
/**
* 根据 startKey 和 endKey 去重新计算每一项 sortKey
*/
sort(startKey, endKey) {
this.setData({itemTransition: true});
let list = this.data.list.map((item) => {
if (item.fixed) return item;
if (startKey < endKey) { // 正序拖动
if (item.sortKey > startKey && item.sortKey <= endKey) {
item.sortKey = this.excludeFix(item.sortKey - 1, startKey, 'reduce');
} else if (item.sortKey === startKey) {
item.sortKey = endKey;
}
return item;
} else if (startKey > endKey) { // 倒序拖动
if (item.sortKey >= endKey && item.sortKey < startKey) {
item.sortKey = this.excludeFix(item.sortKey + 1, startKey, 'add');
} else if (item.sortKey === startKey) {
item.sortKey = endKey;
}
return item;
}
});
this.updateList(list);
},
/**
* 排除固定项得到最终 sortKey
*/
excludeFix(sortKey, startKey, type) {
if (sortKey === startKey) return startKey;
if (this.data.list[sortKey].fixed) {
let _sortKey = type === 'reduce' ? sortKey - 1 : sortKey + 1;
return this.excludeFix(_sortKey, startKey, type);
} else {
return sortKey;
}
},
/**
* 根据排序后 list 数据进行位移计算
*/
updateList(data, vibrate = true) {
let {platform} = this.data;
let list = data.map((item, index) => {
item.tranX = `${(item.sortKey % this.data.columns) * 100}%`;
item.tranY = `${Math.floor(item.sortKey / this.data.columns) * 100}%`;
return item;
});
this.setData({list: list});
if (!vibrate) return;
if (platform !== "devtools") wx.vibrateShort();
this.triggerCustomEvent(list, "change");
},
/**
* 判断是否是固定的 item
*/
isFixed(index) {
let list = this.data.list;
if (list && list[index] && list[index].fixed) return 1;
return 0;
},
/**
* 清除参数
*/
clearData() {
this.setData({
preStartKey: -1,
dragging: false,
cur: -1,
tranX: 0,
tranY: 0
});
// 延迟清空
setTimeout(() => {
this.setData({
curZ: -1,
})
}, 300)
},
/**
* 点击每一项后触发事件
*/
itemClick(e) {
let {index, key} = e.currentTarget.dataset;
let list = this.data.list;
let currentItem = list[index];
if (!currentItem.extraNode) {
let _list = [];
list.forEach((item) => {
_list[item.sortKey] = item;
});
let currentKey = -1;
for (let i = 0, len = _list.length; i < len; i++) {
let item = _list[i];
if (!item.extraNode) {
currentKey++;
}
if (item.sortKey === currentItem.sortKey) {
break;
}
}
this.triggerEvent('click', {
key: currentKey,
data: currentItem.data
});
}
},
/**
* 封装自定义事件
* @param list 当前渲染的数据
* @param type 事件类型
*/
triggerCustomEvent(list, type) {
let _list = [], listData = [];
list.forEach((item) => {
_list[item.sortKey] = item;
});
_list.forEach((item) => {
if (!item.extraNode) {
listData.push(item.data);
}
});
this.triggerEvent(type, {listData: listData});
},
/**
* 初始化获取 dom 信息
*/
initDom() {
let {windowWidth, windowHeight, platform, SDKVersion} = wx.getSystemInfoSync();
this.data.pageMetaSupport = compareVersion(SDKVersion, '2.9.0') >= 0;
let remScale = (windowWidth || 375) / 375,
realTopSize = this.data.topSize * remScale / 2,
realBottomSize = this.data.bottomSize * remScale / 2;
this.data.windowHeight = windowHeight;
this.data.platform = platform;
this.data.realTopSize = realTopSize;
this.data.realBottomSize = realBottomSize;
this.createSelectorQuery().select(".item").boundingClientRect((res) => {
let rows = Math.ceil(this.data.list.length / this.data.columns);
this.data.rows = rows;
this.data.itemDom = res;
console.log(res)
console.log(rows)
this.setData({
itemWrapHeight: rows * res.height,
});
this.createSelectorQuery().select(".item-wrap").boundingClientRect((res) => {
console.log(res)
this.data.itemWrapDom = res;
this.data.itemWrapDom.top += this.data.scrollTop
}).exec();
}).exec();
},
/**
* 初始化函数
* {listData, columns, topSize, bottomSize} 参数改变需要重新调用初始化方法
*/
init() {
this.clearData();
this.setData({itemTransition: false});
let delItem = (item, extraNode) => ({
id: item.dragId,
slot: item.slot,
fixed: item.fixed,
extraNode: extraNode,
tranX: "0%",
tranY: "0%",
data: item
});
let {listData, extraNodes} = this.data;
let _list = [], _before=[], _after=[], destBefore = [], destAfter = [];
extraNodes.forEach((item, index) => {
if(item.type === "before") {
_before.push(delItem(item, true));
} else if(item.type === "after") {
_after.push(delItem(item, true));
} else if(item.type === "destBefore") {
destBefore.push(delItem(item, true));
} else if(item.type === "destAfter") {
destAfter.push(delItem(item, true));
}
});
// 遍历数据源增加扩展项, 以用作排序使用
listData.forEach((item, index) => {
destBefore.forEach((i) => {
if (i.data.destKey === index) _list.push(i);
});
_list.push(delItem(item, false));
destAfter.forEach((i) => {
if (i.data.destKey === index) _list.push(i);
});
});
let list = _before.concat(_list, _after).map((item, index) => {
console.log(item)
item.sortKey = index; // 初始化 sortKey 为当前项索引值
item.tranX = `${(item.sortKey % this.data.columns) * 100}%`;
item.tranY = `${Math.floor(item.sortKey / this.data.columns) * 100}%`;
return item;
});
if (list.length === 0) {
this.setData({itemWrapHeight: 0});
return;
}
this.updateList(list, false);
// 异步加载数据时候, 延迟执行 initDom 方法, 防止基础库 2.7.1 版本及以下无法正确获取 dom 信息
setTimeout(() => this.initDom(), 0);
}
},
ready() {
this.init();
}
});
<view class="item-wrap" style="height: {{ itemWrapHeight }}px;">
<view
class="item {{cur == index ? 'cur':''}} {{curZ == index ? 'zIndex':''}} {{itemTransition && index !== cur ? 'itemTransition':''}} {{item.fixed ? 'fixed' : ''}}"
wx:for="{{list}}"
wx:key="id"
data-key="{{item.sortKey}}"
data-index="{{index}}"
style="transform: translate3d({{index === cur ? tranX + 'px' : item.tranX}}, {{index === cur ? tranY + 'px' : item.tranY}}, 0);width: {{100 / columns}}%"
bindtap="itemClick"
bind:longpress="longPress"
catch:touchmove="{{dragging?'touchMove':''}}"
catch:touchend="{{dragging?'touchEnd':''}}">
<!-- start:请在该区域编写自己的渲染逻辑 -->
<view class="cell" wx:if="{{list.length<=2}}">
<view class="team-item">
<view class="team-info">
<image class="team-image" mode="aspectFill" src="{{item.data.images? item.data.images : 'https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png'}}" alt=""/>
<view class="team-desc">{{item.data.title}}</view>
</view>
<van-swipe-cell>
<template slot="left">
<van-button square type="primary" text="选择" />
</template>
<view class="team-option">
<van-icon name="wap-nav"/>
</view>
<template slot="right">
<van-button square type="danger" text="删除" />
<van-button square type="primary" text="收藏" />
</template>
</van-swipe-cell>
</view>
<view wx:for="{{item.data.rankList}}" wx:for-index="index" wx:key="id" wx:for-item="item1">
<rank-item wx:if="{{item.data.id==item1.id}}" bang-obj="{{item1}}" />
</view>
</view>
<view class="cell" wx:if="{{(index==0||index==1)&&list.length>2}}">
<view class="team-item">
<view class="team-info">
<image class="team-image" mode="aspectFill" src="{{item.data.images? item.data.images : 'https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png'}}" alt=""/>
<view class="team-desc" catchtap="delTeammate">{{item.data.title}}</view>
</view>
<van-swipe-cell>
<template slot="left">
<van-button square type="primary" text="选择" />
</template>
<view class="team-option">
<van-icon name="wap-nav" />
</view>
<template slot="right">
<van-button square type="danger" text="删除" />
<van-button square type="primary" text="收藏" />
</template>
</van-swipe-cell>
</view>
<view style="width:100%" wx:for="{{item.data.rankList}}" wx:for-index="index1" wx:key="id" wx:for-item="item1">
<!-- <rank-item wx:if="{{item.data.id==item1.id}}" bang-obj="{{item1}}" /> -->
<image class="team-image" mode="aspectFill" src="https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png" alt=""/>
<!-- <image wx:if="{{index1==1}}" class="team-image" mode="aspectFill" src="https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png" alt=""/> -->
</view>
<view class="put" wx:if="{{index==1&&!putShow}}" bindtap="openClick">
展开全部
</view>
</view>
<view class="cell" wx:if="{{(index!=0&&index!=1)&&list.length>2&&putShow}}">
<view class="team-item">
<view class="team-info">
<image class="team-image" mode="aspectFill" src="{{item.data.images? item.data.images : 'https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png'}}" alt=""/>
<view class="team-desc">{{item.data.title}}</view>
</view>
<van-swipe-cell>
<template slot="left">
<van-button square type="primary" text="选择" />
</template>
<view class="team-option">
<van-icon name="wap-nav" />
</view>
<template slot="right">
<van-button square type="danger" text="删除" />
<van-button square type="primary" text="收藏" />
</template>
</van-swipe-cell>
</view>
<view style="width:100%" wx:for="{{item.data.rankList}}" wx:for-index="index" wx:key="id" wx:for-item="item1">
<rank-item wx:if="{{item.data.id==item1.id}}" bang-obj="{{item1}}" />
</view>
<view class="put" wx:if="{{index==list.length-1}}" bindtap="putClick">
收起全部
</view>
</view>
<!-- end:请在该区域编写自己的渲染逻辑 -->
</view>
</view>
<van-popup show="{{ show }}" bind:close="onClose" custom-style="width:600rpx;height:380rpx;border-radius: 10px;" >
<view class="pop">
<view class="reason">提示</view>
<view class="title">确认删除队友吗?</view>
<view class="btn">
<van-button custom-class="confirm" bind:click='onClose'>犹豫一下</van-button>
<van-button custom-class="true" bind:click="updateTeammate">暂时删除</van-button>
</view>
</view>
</van-popup>
找轮子,不过一般都要根据自己的情况改的,基本思路就是使用movable-view来做,布局那块你要自己写函数生成布局的x和y,当拖动一个块到另一个块上触发touchend的时候对比一下x和y判断是在哪个块上,然后触发位置交换,直接交换两个块的x和y就行。大概思路就这样,细节的慢慢抠一般能写出来。