前言
- 图片拖拽排序是一个比拟罕用的组件,罕用于发帖或者评论等内容上传模块,我就借鉴了《一款优雅的小程序拖拽排序组件实现》这篇文章的实现思路,并封装成 wx-drag-img 公布到 npm
- 实现原理:每个图片初始化我都会封装成一个拖拽的数据结构,而后通过触发 touch 事件,扭转 transform 地位,从而达到拖拽成果
- 性能包含图片上传拖拽删除,源码和 npm 地址我会贴在结尾,如果感觉好的话,欢送 star
- 我会在上面逐渐剖析这个组件的实现思路
-
应用了以下变量
// 拖拽数据结构 interface IDragImg { src: string; // 图片门路 key: number; // id: number; // for 循环遍历应用, 不会扭转, 创立时自增 id tranX: number; // x 轴位移间隔 tranY: number; // y 轴位移间隔 } // props { previewSize // 图片大小 defaultImgList // 初始化图片数组 maxCount // 图片上传数量限度 columns // 列数 gap // 图片距离 deleteStyle // 删除款式 } data: {dragImgList: IDragImg[], containerRes: { top: 0, // 容器间隔页面顶部间隔 px left: 0, // 容器间隔页面右边间隔 px width: 0, // 容器宽度 px height: 0, // 容器高度 px }, // 拖拽容器属性 currentKey: -1, // 正在拖拽图片的 key currentIndex: -1, // 正在拖拽图片的 index tranX: 0, // 正在拖拽图片挪动的 x 间隔 tranY: 0, // 正在拖拽图片挪动的 y 间隔 uploadPosition: { // upload 上传图标位移间隔 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); } },
-
上传后须要初始化拖拽的数据结构
/** * 依据图片列表生成拖拽列表数据结构 * @param list 图片 src 列表 * @param init 是否是初始化 */ 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, }; }); },
-
批改上传区域地位
/** * 批改上传区域地位 * @param listLength 数组长度 */ 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, }); },
-
图片数量扭转后就要从新获取容器宽高了
/** * 扭转图片数量后获取容器宽高 * @parma listLength 数组长度 */ 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 * @describe 上传删除拖拽后触发事件把列表数据发给页面 */ 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); // 大于删除图片 key 的 key 全副减 1 list.forEach((item) => {item.key > key && item.key--;}); // 获取 this.getListPosition(list); this.setUploaPosition(list.length); },
/** * 计算数组的位移地位 * @param list 拖拽图片数组 */ 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 */ touchMove(e) { // 如果 currentIndex < 0,阐明并没有触发 longPress 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 }); // 比照以后挪动的 key 和停放地位的 key,如果不一样就批改地位 const currentKey = e.mark.key; const moveKey = this.getMoveKey(tranX, tranY); // 当挪动的 key 和正在停放地位的 key 相等,就毋庸解决 if (currentKey === moveKey || this.data.currentKey === currentKey) {return;} this.data.currentKey = currentKey; this.replace(currentKey, moveKey); },
-
getMoveKey
/** * 计算挪动中的 key * @param tranX 正在拖拽图片的 tranX * @param tranY 正在拖拽图片的 tranY */ 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
/** * 生成拖拽后的新数组 * @param start 拖拽起始的 key * @param end 拖拽完结的 key */ 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 */ touchEnd() { this.setData({ tranX: 0, tranY: 0, currentIndex: -1, }); this.data.currentKey = -1; },
总结
- repo
- npm
参考资料
- 一款优雅的小程序拖拽排序组件实现