前言:最近遇到一个需求,需要图片上传之后,可以拖拽图片改变图片展示的顺序。我经过搜索发现目前 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 的一些动画细节和操作细节并没有优化的特别好,但是:
使用 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 元素,而不会整个删除并重新渲染了。
(完)