微信小程序图片上传&九宫格拖拽组件

微信小程序图片上传&九宫格拖拽组件

前言

  • 图片上传加九宫格拖拽是一个比较常用的组件,常用于发帖或者评论等内容上传模块,我这篇九宫格拖拽的思路是借鉴了一款优雅的小程序拖拽排序组件实现这篇文章

  • 实现效果如下图
    请添加图片描述

  • 实现原理:新增图片时,为每一个图片增加一个key属性,和tranX、tranY属性,用于transform位移,然后这个九宫格的拖拽新增删除都是以key为标记来修改tranX和tranY,从而达到拖拽效果

  • 使用了以下变量

    data: {
      ITEM_SIZE: 100, // 图片大小 单位px
      dragImgList: [], // 图片列表 { src: string, key: number, tranX: number, tranY: number }[]
      containerRes: {}, // 拖拽容器属性
      currentKey: -1, // 正在拖拽图片的key
      currentIndex: -1, // 正在拖拽图片的index
      tranX: 0, // 正在拖拽图片移动的x距离
      tranY: 0, // 正在拖拽图片移动的y距离
      uploadPosition: { // upload上传图标位移距离
        tranX: 0,
        tranY: 0,
      }
    },

WXML & WXSS

<view class="drag-container">
  <view
    wx:for="{{dragImgList}}" 
    wx:key="index"
    style="transform: translate({{index === currentIndex ? tranX : item.tranX}}px, {{index === currentIndex ? tranY : item.tranY}}px); z-index: {{index === currentIndex ? 10 : 1}};"
    class="darg-item item-transition"
    mark:index="{{index}}"
    mark:key="{{item.key}}"
    catch:longpress="longPress"
    catch:touchmove="touchMove"
    catch:touchend="touchEnd"
  >
    <image class="darg-item-img" src="{{item.src}}"/>
    <text catch:tap="deleteImg" mark:key="{{item.key}}" class="drag-item-delete">×</text>
  </view>
  <view
    bindtap="uploadImage"
    class="darg-item drag-item-upload"
    hidden="{{dragImgList.length >= 9}}"
    style="transform: translate({{uploadPosition.tranX}}px, {{uploadPosition.tranY}}px);"
  >+</view>
</view>
.drag-container{
  position: relative;
  width: 300px;
  height: 300px;
}
.item-transition {
  transition: transform 0.1s
}
.darg-item{
  position: absolute;
  top: 0;
  left: 0;
  width: 100px;
  height: 100px;
}
.darg-item-img{
  width: 100%;
  height: 100%;
}
.drag-item-delete{
  position: absolute;
  top: 0;
  right: 0;
  width: 58rpx;
  height: 33rpx;
  background-color: rgba(0,0,0,0.7);
  font-size: 47rpx;
  line-height: 25rpx;
  text-align: center;
  border-radius: 0 0 0 64rpx;
  color: #fff;
}
.drag-item-upload{
  background-color: burlywood;
  font-size: 200rpx;
  text-align: center;
  color: white;
  line-height: 200rpx;
}

图片上传

  • 图片上传很简单,就是初始化上传的图片,然后拼接图片列表,最后修改上传图标位置即可
    uploadImage() {
      let { dragImgList, ITEM_SIZE } = this.data
      wx.chooseImage({
        count: 9 - dragImgList.length,
        success: (res) => {
          const imgList = res.tempFilePaths.map((item, index) => ({
            tranX: ITEM_SIZE * ((dragImgList.length + index) % 3),
            tranY: Math.floor((dragImgList.length + index) / 3) * ITEM_SIZE,
            src: item,
            key: dragImgList.length + index
          }))
          dragImgList = dragImgList.concat(imgList)
          // 修改上传图标位置
          this.setUploaPosition(dragImgList.length)
          this.setData({
            dragImgList,
          })
        }
      })
    },
    setUploaPosition(listLength) {
      const ITEM_SIZE = this.data.ITEM_SIZE
      const uploadPosition = {
        tranX: listLength % 3 * ITEM_SIZE,
        tranY: Math.floor(listLength / 3) * ITEM_SIZE,
      }
      this.setData({
        uploadPosition,
      })
    },

图片删除

  • 首先从图片列表中删除所需图片,然后修改列表,把大于所选图片key的key全部减一,最后修改剩余图片位置和上传图标位置
    deleteImg(e) {
      const key = e.mark.key
      // 删除图片
      const list = this.data.dragImgList.filter((item) => item.key !== key)
      // 修改key值
      list.forEach((item) => item.key > key && item.key--)
      // 修改剩余图片位置
      this.getListPosition(list)
      // 修改上传图标位置
      this.setUploaPosition(list.length)
    }
    getListPosition(list) {
      const ITEM_SIZE = this.data.ITEM_SIZE
      const dragImgList = list.map((item) => {
        item.tranX = ITEM_SIZE * (item.key % 3);
        item.tranY = Math.floor(item.key / 3) * ITEM_SIZE;
        return item
      })
      this.setData({
        dragImgList,
      })
    
      // 向页面传递最新图片列表
      const urlList = [...dragImgList].sort((a, b) => a.key - b.key).map((item) => item.src)
      this.triggerEvent('updateImage', {
        list: urlList
      })
    },

九宫格拖拽

初始化

  • 初始化只需获取容器的位置信息即可,因为拖拽组件不一定是在页面的左上角,所以需要知道容器的位置信息
    lifetimes: {
      ready() {
        this.createSelectorQuery()
          .select(".drag-container")
          .boundingClientRect((res) => {
            this.data.containerRes = res
          }).exec();
      }
    },

longPress

  • 这里采用longPress,而不是touchStart的原因,一是为了优化体验,二是touchStart与删除按钮冲突
    longPress(e) {
      const index = e.mark.index
      const { pageX, pageY } = e.touches[0]
      const { top, left } = this.data.containerRes
      this.setData({
        currentIndex: index,
        tranX: pageX - 50 - left,
        tranY: pageY - 50 - top
      })
    },

touchMove

  • touchMove首先计算出位移距离,然后根据位移距离求出停放位置的key,如果不一样就修改位置
    touchMove(e) {
      // 如果currentIndex < 0,说明并没有触发longPress
      if (this.data.currentIndex < 0) return
      const { pageX, pageY } = e.touches[0]
      const { top, left } = this.data.containerRes
      const tranX = pageX - 50 - left
      const tranY = pageY - 50 - top
      this.setData({
        tranX,
        tranY
      })
      // 对比当前移动的key和停放位置的key,如果不一样就修改位置
      const currentKey = e.mark.key
      const moveKey = this.getMoveKey(tranX, tranY)
      if (currentKey === moveKey || this.data.currentKey === currentKey) return
      this.data.currentKey = currentKey
      this.insert(currentKey, moveKey)
    },
    getMoveKey(tranX, tranY) {
      const { dragImgList: list, ITEM_SIZE } = this.data
      const _getPositionNumber = (position) => {
        const positionNumber = Math.round(position/ ITEM_SIZE)
        return positionNumber > 2 ? 2 : positionNumber < 0 ? 0 : positionNumber
      }
      const endKey = 3 * _getPositionNumber(tranY) + _getPositionNumber(tranX)
      return endKey >= list.length ? list.length - 1 : endKey
    },
    insert(origin, end) {
      const dragImgList = this.data.dragImgList
      dragImgList.forEach((item) => {
        if (origin < end) {
          // 如果起始key小于结束key,就把区间内的key全部减一
          if (item.key > origin && item.key <= end) item.key--
          else if (item.key === origin) item.key = end
        } else if (origin > end) {
          // 如果起始key大于结束key,就把区间内的key全部加一
          if (item.key >= end && item.key < origin) item.key++
          else if (item.key === origin) item.key = end
        }
      })
      this.getListPosition(dragImgList)
    },

touchEnd

  • touchEnd用于重置数据
    touchEnd() {
      this.setData({
        tranX: 0,
        tranY: 0,
        currentIndex: -1,
      })
      this.data.currentKey = -1
    },

总结

  • 代码片段https://developers.weixin.qq.com/s/2rFIJamX7xty