前言

  • 图片拖拽排序是一个比拟罕用的组件,罕用于发帖或者评论等内容上传模块,我就借鉴了《一款优雅的小程序拖拽排序组件实现》这篇文章的实现思路,并封装成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

参考资料

  • 一款优雅的小程序拖拽排序组件实现