关于微信小程序:微信小程序图片拖拽排序组件

48次阅读

共计 6375 个字符,预计需要花费 16 分钟才能阅读完成。

前言

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

参考资料

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

正文完
 0