[WIP]拖拽列表的原生实现

本文介绍了如何利用HTML拖放API在没有第三方库支持的情况下,实现列表项目的拖拽排序功能。通过设置元素的dragStatus、dragOver和dragPosition属性来控制渲染效果,监听拖放事件更新状态,并在拖放结束时计算新的排序。同时,文章提到了react-sortable-hoc、react-dnd和react-beautiful-dnd等第三方库作为替代方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

背景: 需求需要实现一个勾选列表,支持拖拽排序。由于组件库不支持,加上想顺便学习下drag API,于是用原生方法实现了一下。

HTML 拖放 API

HTML 拖放(Drag and
Drop)接口使应用程序能够在浏览器中使用拖放功能。例如,用户可使用鼠标选择可拖拽(draggable)元素,将元素拖拽到可放置(droppable)元素,并释放鼠标按钮以放置这些元素。拖拽操作期间,会有一个可拖拽元素的半透明快照跟随着鼠标指针。

详见MDN文档:HTML 拖放 API

生命周期流程如图:
在这里插入图片描述

实现思路

首先拆分组件为列表和每一项列表项。

对于每个列表项,保存三个属性用于渲染

  • dragStatus:是否在拖拽中,用于渲染拖拽样式
  • dragOver:是否被覆盖,用于渲染被覆盖时的样式
  • dragPosition:拖拽方向。释放元素时,位于覆盖元素的上侧还是下侧,用于处理排序逻辑

在对应的生命周期更新属性:

 <li
      draggable={true}
      className={getClass({
        [`${baseClassName}-draggable`]: !!draggable,
        [`${baseClassName}-${dragStatus}`]: dragStatus !== 'none',
        [`${baseClassName}-gap-top`]: dragStatus !== 'dragging' && dragOver && dragPosition < 0,
        [`${baseClassName}-gap-bottom`]: dragStatus !== 'dragging' && dragOver && dragPosition > 0,
      })}
      onDragStart={(e) => {
        e.stopPropagation()
        setDragStatus('dragging')
      }}
      onDragEnd={(e) => {
        e.stopPropagation()
        setDragStatus('none')
      }}
      onDragOver={(e) => {
        e.stopPropagation()
        e.preventDefault()

        const rect = itemRef.current?.getBoundingClientRect()
        const threshold = window.pageYOffset + rect.top + rect.height / 2
        const position = e.pageY > threshold ? 1 : -1

        setDragOver(true)
        setDragPosition(position)
      }}
      onDrop={(e) => {
        e.stopPropagation()
        e.preventDefault()
        setDragOver(false)
        setDragPosition(0)
        setDragStatus('none')
        if (onDrop) onDrop(e, item, dragPosition)
      }}
      onDragLeave={(e) => {
        e.stopPropagation()
        setDragOver(false)
      }}
    >
      {itemContent}
    </li>

关于计算拖动释放时排列位置
在这里插入图片描述

对于列表,在拖拽开始&结束时记录拖拽元素,在释放时记录释放元素&拖拽方向,并计算得出排序后的数组。

<div>
          {dataSource.map((item) => (
            <Item
              item={item}
              onDragStart={(e, item: ITransferItem) => {
                setDragItem(item)
              }}
              onDragEnd={(e, item: ITransferItem) => {
                setDragItem(null)
              }}
              onDrop={(e: DragEvent<HTMLSpanElement>, dropItem: ITransferItem, dropPosition: number) => {
                if (dragItem && dragItem.key !== dropItem.key) {
                  if (onDrop)
                    onDrop({
                      e,
                      dropItem,
                      dropPosition,
                      dragItem,
                      dataSource,
                    })
                }
              }}
            />
          ))}
        </div>

关于数组按顺序移动

const moveArrayItem = (arr: any[], fromIndex: number, toIndex: number) => {
  const ans = [...arr]
  if (fromIndex > toIndex) {
    ans.splice(toIndex, 0, ans[fromIndex])
    ans.splice(fromIndex + 1, 1)
  } else {
    ans.splice(toIndex + 1, 0, ans[fromIndex])
    ans.splice(fromIndex, 1)
  }
  return ans
}

第三方库

  • react-sortable-hoc
  • react-dnd
  • react-beautiful-dnd
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值