草庐IT

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

LH_R 2023-09-01 原文

前言

  • 图片拖拽排序是一个比较常用的组件,常用于发帖或者评论等内容上传模块,我借鉴了《一款优雅的小程序拖拽排序组件实现》这篇文章的拖拽思路,封装成wx-drag-img发布到npm

  • 实现原理:每个图片初始化我都会封装成一个拖拽的数据结构,然后通过触发touch事件,根据key的变化改变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;
},

总结

参考资料

有关微信小程序图片拖拽排序组件的更多相关文章

  1. ruby-on-rails - Ruby on Rails - 为文本区域和图片生成列 - 2

    我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数

  2. 微信小程序通过字典表匹配对应数据 - 2

    前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立

  3. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  4. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  5. ruby-on-rails - 需要帮助最大化多个相似对象中的 3 个因素并适当排序 - 2

    我需要用任何语言编写一个算法,根据3个因素对数组进行排序。我以度假村为例(如Hipmunk)。假设我想去度假。我想要最便宜的地方、最好的评论和最多的景点。但是,显然我找不到在所有3个中都排名第一的方法。Example(assumingthereare20importantattractions):ResortA:$150/night...98/100infavorablereviews...18of20attractionsResortB:$99/night...85/100infavorablereviews...12of20attractionsResortC:$120/night

  6. ruby-on-rails - 在具有 ActiveRecord 条件的相关模型中按字段排序 - 2

    我正在尝试按Rails相关模型中的字段进行排序。我研究的所有解决方案都没有解决如果相关模型被另一个参数过滤?元素模型classItem相关模型:classPriority我正在使用where子句检索项目:@items=Item.where('company_id=?andapproved=?',@company.id,true).all我需要按相关表格中的“位置”列进行排序。问题在于,在优先级模型中,一个项目可能会被多家公司列出。因此,这些职位取决于他们拥有的company_id。当我显示项目时,它是针对一个公司的,按公司内的职位排序。完成此任务的正确方法是什么?感谢您的帮助。PS-我

  7. ruby - 按数字(从大到大)然后按字母(字母顺序)对对象集合进行排序 - 2

    我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby​​做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排

  8. ruby-on-rails - 在不重新查询数据库的情况下重新排序 Rails 中的事件记录? - 2

    例如,假设我有一个名为Products的模型,并且在ProductsController中,我有以下代码用于product_listView以显示已排序的产品。@products=Product.order(params[:order_by])让我们想象一下,在product_listView中,用户可以使用下拉菜单按价格、评级、重量等进行排序。数据库中的产品不会经常更改。我很难理解的是,每次用户选择新的order_by过滤器时,rails是否必须查询,或者rails是否能够以某种方式缓存事件记录以在服务器端重新排序?有没有一种方法可以编写它,以便在用户排序时rails不会重新查询结果

  9. ruby-on-rails - Rails 3,在RAILS_ROOT上方显示来自本地文件系统的jpg图片 - 2

    我正在尝试找出一种方法来显示来自不在RAILS_ROOT下(在RedHat或Ubuntu环境中)的已安装文件系统的图像。我不想使用符号链接(symboliclink),因为这个应用程序实际上是通过Tomcat部署的,而当我关闭Tomcat时,Tomcat会尝试跟随符号链接(symboliclink)并删除挂载中的所有图像。由于这些文件的数量和大小,将图像放在public/images下也不是一种选择。我查看了send_file,但它只会显示一张图片。我需要在一个格式良好的页面中显示6个请求的图像。由于膨胀,我宁愿不使用Base64编码,但我不知道如何将图像数据与呈现的页面一起传递下去。

  10. ruby-on-rails - 如何对对象数组进行排序? - 2

    我有一个对象如下:[{:id=>2,:fname=>"Ron",:lname=>"XXXXX",:photo=>"XXX"},{:id=>3,:fname=>"Dain",:lname=>"XXXX",:photo=>"XXXXXXX"},{:id=>1,:fname=>"Bob",:lname=>"XXXXXX",:photo=>"XXXX"}]我想按fname排序,不区分大小写,所以它会导致编号:1,3,2我该如何排序?我正在尝试:@people.sort!{|x,y|y[:fname]x[:fname]}但这没有任何效果。 最佳答案

随机推荐