如何基于 antd Upload 组件的图片墙实现拖拽排序

前言:最近遇到一个需求,需要图片上传之后,可以拖拽图片改变图片展示的顺序。我经过搜索发现目前 antdUpload 组件并未支持拖拽排序功能,网上也没发现可以借鉴的例子,于是我自己基于 react-sortable-hoc 实现了这个效果,如有需要可以参考。


最终效果及源码

预览
  • 源码地址,欢迎star,欢迎pr
  • 基于 antd@4.4.2react-sortable-hoc@1.11.0typescript@3.8.2

技术选型

市面上可以实现拖拽排序的库有很多,比如 SortableJSreact-dndreact-beautiful-dndreact-sortable-hoc等。上面列出来的几个库我都大致了解了一遍,下面我将介绍自己是如何选择这几个库的。

SortableJS

首先考虑的是 SortableJS,它足够轻量级,而且功能齐全,但是在 React 中使用起来并不是太方便,而且它的配置项写起来实在不太符合 React 的思维,PASS。

react-dnd 和 react-beautiful-dnd

这两个库是专门为 react 使用者写的库,在 react中用起来很顺畅。但是 react-dnd 拖拽的动画效果一般,react-beautiful-dnd 的动画效果和细节非常完美。所以我在最开始选用的是 react-beautiful-dnd,在源码中也能发现实际上我还基于 react-beautiful-dnd 实现了一个版本 PicturesWall 组件。但是后来发现,这两个库其实都只支持同一个方向上的拖拽,如果是多行的图片墙,效果就非常不好,所以最终我选择了下面的 react-sortable-hoc

react-sortable-hoc

这就是我最终的选择,专门为 React 使用者开发的拖拽排序库,支持多行拖拽排序,动画效果也还可以。当然这也不是完美的方案,因为在实际体验中,react-sortable-hoc 的一些动画细节和操作细节并没有优化的特别好,但是:

又不是不能用.jpg

使用 react-sortable-hoc 实现拖拽排序

完整代码详见 PicturesGrid 组件。下面会列一些核心代码:

  • SortableItem:
const SortableItem = SortableElement((params: SortableItemParams) => (
  <div style={itemStyle}>
    <UploadList
      locale={{ previewFile: '预览图片', removeFile: '删除图片' }}
      showDownloadIcon={false}
      listType={params.props.listType}
      onPreview={params.onPreview}
      onRemove={params.onRemove}
      items={[params.item]}
    />
  </div>
));
  • SortableList
const SortableList = SortableContainer((params: SortableListParams) => {
  return (
    <div style={listStyle}>
      {params.items.map((item, index) => (
        <SortableItem
          key={`${item.uid}`}
          index={index}
          item={item}
          props={params.props}
          onPreview={params.onPreview}
          onRemove={params.onRemove}
        />
      ))}
      <Upload
        {...params.props}
        showUploadList={false}
        onChange={params.onChange}
      >
        {params.props.children}
      </Upload>
    </div>
  );
});
  • PicturesGrid 核心代码
<>
  <SortableList
    // 当移动 1 之后再触发排序事件,默认是0,会导致无法触发图片的预览和删除事件
    distance={1}
    items={fileList}
    onSortEnd={onSortEnd}
    axis="xy"
    helperClass="SortableHelper"
    props={props}
    onChange={onChange}
    onRemove={onRemove}
    onPreview={onPreview}
  />
  <Modal
    visible={!!previewImage}
    footer={null}
    onCancel={() => setPreviewImage('')}
    bodyStyle={{ padding: 0 }}
  >
    <img style={{ width: '100%' }} alt="" src={previewImage} />
  </Modal>
</>

遇到的问题和解决方案

图片墙上的按钮无法点击

在 antd 的 Upload 组件中,图片墙上会有「预览」、「删除」等按钮,但是在 react-sortable-hoc 的逻辑中,只要我点击了图片,就会触发图片的拖拽函数,无法触发图片上的各种按钮,所以需要在 SortableList上重新设置一下 distance 属性,设置成 1 即可。

<SortableList
  distance={1}
/>

该属性的含义在官网是这样介绍的:

If you'd like elements to only become sortable after being dragged a certain number of pixels. Cannot be used in conjunction with the pressDelay prop.
(如果您希望元素仅在拖动一定数量的像素后才可排序。不能与pressDelay道具一起使用。默认为 0

所以设置 distance1,就可以达成当我们点击图片按钮不触发拖拽的效果

换行问题

如上图,从第二行拖拽至第一行,第一行的内容会超出容器,我搜到了这个 issue,通过其中的回复:

Because my container wasn't scrollable, it bubbled up to grab the width of the window. I added overflow: auto to my container and the issue was solved. …… …… Which element are you adding overflow: auto to? It should be the first parent element of the sortable elements。
(因为我的容器不可滚动,所以它冒泡使得超出了窗口的宽度。我为我的容器添加了overflow: auto,得以解决问题。您向哪个元素添加的overflow: auto?应该向可排序元素的第一个父元素添加这个属性。)

向可排序元素的第一个父元素添加这个属性添加了 overflow: auto,解决了问题。

无法上传

当我一开始完成了 PicturesGrid 组件的开发,测试发现:无法上传图片,每次发起上传图片的请求,该请求的状态会立刻变为 canceled。经过搜索,发现下面这种情况会导致请求canceled

The DOM element that caused the request to be made got deleted (i.e. an IMG is being loaded, but before the load happened, you deleted the IMG node)。
(导致发出请求的DOM元素被删除(即,正在加载IMG,但是在加载之前,您已删除IMG节点))

在通过审查元素,终于找到了原因:当图片列表发生变化,整个sortable容器被删除并重新渲染,导致请求失效。解决方案是:需要将 SortableItemSortableList 写在 React.FC 外面,每次组件内部 state 发生变化,不会重新执行 SortableContainerSortableElement方法,就可以让 可排序容器里面的元素自动只更新需要改变的 DOM 元素,而不会整个删除并重新渲染了。

(完)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354