前言
- 图片拖拽排序是一个比较常用的组件,常用于发帖或者评论等内容上传模块,我借鉴了《一款优雅的小程序拖拽排序组件实现》这篇文章的拖拽思路,并封装成wx-drag-img发布到npm
- 实现原理:每个图片初始化我都会封装成一个拖拽的数据结构,然后触发touch事件时,通过改变图片的key来计算transform位置,从而达到拖拽效果
- 功能包括图片上传拖拽删除,源码和npm地址我会贴在结尾,如果感觉好的话,欢迎star
- 我会在下面逐步分析这个组件的实现思路
- 使用了以下变量
interface IDragImg {
src: string;
key: number;
id: number;
tranX: number;
tranY: number;
}
{
previewSize
defaultImgList
maxCount
columns
gap
deleteStyle
}
data: {
dragImgList: IDragImg[],
containerRes: {
top: 0,
left: 0,
width: 0,
height: 0,
},
currentKey: -1,
currentIndex: -1,
tranX: 0,
tranY: 0,
uploadPosition: {
tranX: 0,
tranY: 0,
}
},
WXML
<view style="width: {{containerRes.width}}px; height: {{containerRes.height}}px;" class="drag-container">
<view
wx:for="{{dragImgList}}"
wx:key="id"
style="transform: translate({{index === currentIndex ? tranX : item.tranX}}px, {{index === currentIndex ? tranY : item.tranY}}px); z-index: {{index === currentIndex ? 10 : 1}}; width: {{previewSize}}px; height: {{previewSize}}px;"
class="drag-item drag-item-transition"
mark:index="{{index}}"
mark:key="{{item.key}}"
catch:longpress="longPress"
catch:touchmove="touchMove"
catch:touchend="touchEnd"
>
<image class="drag-item-img" src="{{item.src}}"/>
<view catch:tap="deleteImg" mark:key="{{item.key}}" class="drag-item-delete">
<view class="drag-item-delete_default" style="{{deleteStyle}}">x</view>
</view>
</view>
<view
bindtap="uploadImage"
class="drag-item drag-upload"
hidden="{{dragImgList.length >= maxCount}}"
style="transform: translate({{uploadPosition.tranX}}px, {{uploadPosition.tranY}}px); width: {{previewSize}}px; height: {{previewSize}}px;"
>
<view class="drag-upload_solt">
<slot name="upload"></slot>
</view>
<view class="drag-upload_default">
<text>+</text>
</view>
</view>
</view>
图片上传
- 图片上传很简单,就是初始化上传的图片,然后拼接到现有图片,最后修改上传图标位置即可
- 点击上传区域回调函数
uploadImage
async uploadImage() {
let { dragImgList, maxCount } = this.data;
try {
const res = await wx.chooseMedia({
count: maxCount - dragImgList.length,
mediaType: ['image'],
});
const imgList = this.getDragImgList(res?.tempFiles?.map(({ tempFilePath }) => tempFilePath) || [], false);
dragImgList = dragImgList.concat(imgList);
this.setUploaPosition(dragImgList.length);
this.setData({
dragImgList,
});
this.updateEvent(dragImgList);
} catch (error) {
console.log(error);
}
},
- 上传后需要初始化拖拽的数据结构
getDragImgList(list, init = true) {
let { dragImgList, previewSize, columns, gap } = this.data;
return list.map((item, index) => {
const i = (init ? 0 : dragImgList.length) + index;
return {
tranX: (previewSize + gap) * (i % columns),
tranY: Math.floor(i / columns) * (previewSize + gap),
src: item,
id: i,
key: i,
};
});
},
- 修改上传区域位置
setUploaPosition(listLength) {
const { previewSize, columns, gap } = this.data;
const uploadPosition = {
tranX: listLength % columns * (previewSize + gap),
tranY: Math.floor(listLength / columns) * (previewSize + gap),
};
const { width, height } = this.getContainerRect(listLength);
this.setData({
uploadPosition,
['containerRes.width']: width,
['containerRes.height']: height,
});
},
- 图片数量改变后就要重新获取容器宽高了
getContainerRect(listLength) {
const { columns, previewSize, maxCount, gap } = this.data;
const number = listLength === maxCount ? listLength : listLength + 1;
const row = Math.ceil(number / columns)
return {
width: columns * previewSize + (columns - 1) * gap,
height: row * previewSize + gap * (row - 1),
};
},
- updateEvent
updateEvent(dragImgList) {
const list = [...dragImgList].sort((a, b) => a.key - b.key).map((item) => item.src);
this.triggerEvent('updateImageList', {
list,
});
},
图片删除
- 首先从图片列表中删除所需图片,然后修改列表,把大于所选图片key的key全部减一,最后计算剩余图片位置和上传图标位置
deleteImg(e) {
const key = e.mark.key;
const list = this.data.dragImgList.filter((item) => item.key !== key);
list.forEach((item) => {
item.key > key && item.key--;
});
this.getListPosition(list);
this.setUploaPosition(list.length);
},
getListPosition(list) {
const { previewSize, columns, gap } = this.data;
const dragImgList = list.map((item) => {
item.tranX = (previewSize + gap) * (item.key % columns);
item.tranY = Math.floor(item.key / columns) * (previewSize + gap);
return item;
})
this.setData({
dragImgList,
});
this.updateEvent(dragImgList);
},
图片拖拽
初始化
- 初始化只需获取容器的位置信息即可,因为拖拽组件不一定是在页面的左上角,所以需要知道容器的位置信息
lifetimes: {
ready() {
this.createSelectorQuery()
.select(".drag-container")
.boundingClientRect(({ top, left }) => {
this.setData({
['containerRes.top']: top,
['containerRes.left']: left,
});
}).exec();
}
},
longPress
- 这里采用longPress,而不是touchStart的原因,一是为了优化体验,二是touchStart与删除按钮冲突
longPress(e) {
const index = e.mark.index;
const { pageX, pageY } = e.touches[0];
const { previewSize, containerRes: { top, left } } = this.data;
this.setData({
currentIndex: index,
tranX: pageX - previewSize / 2 - left,
tranY: pageY - previewSize / 2 - top,
});
},
touchMove
- touchMove首先计算出位移距离,然后根据位移距离求出停放位置的key,如果不一样就修改位置
- touchMove
touchMove(e) {
if (this.data.currentIndex < 0) {
return;
}
const { pageX, pageY } = e.touches[0];
const { previewSize, containerRes: { top, left } } = this.data;
const tranX = pageX - previewSize / 2 - left;
const tranY = pageY - previewSize / 2 - top;
this.setData({
tranX,
tranY
});
const currentKey = e.mark.key;
const moveKey = this.getMoveKey(tranX, tranY);
if (currentKey === moveKey || this.data.currentKey === currentKey) {
return;
}
this.data.currentKey = currentKey;
this.replace(currentKey, moveKey);
},
- getMoveKey
getMoveKey(tranX, tranY) {
const { dragImgList: list, previewSize, columns } = this.data;
const _getPositionNumber = (drag, limit) => {
const positionNumber = Math.round(drag / previewSize);
return positionNumber >= limit ? limit - 1 : positionNumber < 0 ? 0 : positionNumber;
}
const endKey = columns * _getPositionNumber(tranY, Math.ceil(list.length / columns)) + _getPositionNumber(tranX, columns);
return endKey >= list.length ? list.length - 1 : endKey;
},
- replace
replace(start, end) {
const dragImgList = this.data.dragImgList;
dragImgList.forEach((item) => {
if (start < end) {
if (item.key > start && item.key <= end) item.key--;
else if (item.key === start) item.key = end;
} else if (start > end) {
if (item.key >= end && item.key < start) item.key++;
else if (item.key === start) item.key = end;
}
});
this.getListPosition(dragImgList);
},
touchEnd
- touchEnd用于重置数据
touchEnd() {
this.setData({
tranX: 0,
tranY: 0,
currentIndex: -1,
});
this.data.currentKey = -1;
},
总结
参考资料