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

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


最终效果及源码

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

技术选型

市面上可以实现拖拽排序的库有很多,比如 SortableJS、react-dnd 、react-beautiful-dnd 、react-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)

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

换行问题

如上图,从第二行拖拽至第一行,第一行的内容会超出容器,我搜到了这个 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容器被删除并重新渲染,导致请求失效。解决方案是:需要将 SortableItem,SortableList 写在 React.FC 外面,每次组件内部 state 发生变化,不会重新执行 SortableContainer和SortableElement方法,就可以让 可排序容器里面的元素自动只更新需要改变的 DOM 元素,而不会整个删除并重新渲染了。

(完)

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容