React原生拖拽API实现拖拽及带动画的排序

需求:

React原生拖拽API实现拖拽及带动画的排序

难点:

  1. React原生拖拽各个API函数的用途
  2. 排序动画的实现思路

实现过程:

​ 拖拽变换位置还算比较顺利,只要拖拽API了解就问题不大,感觉难点主要在动画的实现,动画的实现主要参考了该文章:js drag拖动排序,但需求上有两点跟该文章的不太一样:一个是用React实现,还有就是该文章的动画思路是先替换dom元素的位置再进行动画效果展示。然后我打算先动画再替换位置。

该文章的动画具体实现思路如下:

先记录两个元素的位置,再将dom元素进行替换位置(是用的判断前后顺序然后用target.parentNode.insertBefore插入),然后再用translate不带transition动画效果的诺回原来的位置,然后利用setTimeout进入下一轮事件循环重置位置、打开动画、诺回目标位置

我的动画实现思路:

获取当前最新的两个元素位置,加上动画transition,用translate先挪位置,然后用定时器设置和动画同样长的时间后去变换实际的dom位置(因为是用的react即直接修改源列表数据),然后因为onDragOver是持续不断的触发,在变换位置的时候,鼠标会碰到元素(猜测是这个原因)导致动画停在中间,我是简单用了flag变量去判断何时去允许下一次动画变位置。

还有个要考虑的点是动画transition何时开始结束,因为变换dom实际位置的时候也还会带着transition

代码:

import { useState } from 'react'
import styles from './index.less';

const INIT_DATA = [
  { id: 1, name: '需求1' },
  { id: 2, name: '需求2' },
  { id: 3, name: '需求3' },
  { id: 4, name: '需求4' },
  { id: 5, name: '需求5' },
  { id: 6, name: '需求6' },
  { id: 7, name: '需求7' },
  { id: 8, name: '需求8' },
  { id: 9, name: '需求9' },
  { id: 10, name: '需求10' },
]

export default function index() {
  const [list, setList] = useState(INIT_DATA)
  const [curDragItem, setCurDragItem] = useState<any>({});
  let animationFlag = true;

  const dragStart = (e: any) => {
    setCurDragItem(JSON.parse(e.target.getAttribute('drag-data')));
  }

  const dragEnd = (e: any) => {
    setCurDragItem({});
  }

  const onDragOver = (e: any) => {
    e.preventDefault();
    let targetItem = JSON.parse(e?.target?.getAttribute('drag-data'));

    if (animationFlag && curDragItem.id && targetItem?.id && curDragItem.id !== targetItem?.id) {
      animationFlag = false;
      // 获取当前拖拽节点最新的位置
      let curNewestDragItemDom: any = getDomItem(curDragItem.id);
      let curNewestDragItemDomRect: any =
        curNewestDragItemDom?.getBoundingClientRect() || {};
      const targetRect = e.target.getBoundingClientRect();
      // 动画
      e.target.style.transition = 'all 200ms';
      curNewestDragItemDom.style.transition = 'all 200ms';

      e.target.style.transform = `translate3d(${curNewestDragItemDomRect.left - targetRect.left
        }px,${curNewestDragItemDomRect.top - targetRect.top}px,0)`;
      curNewestDragItemDom.style.transform = `translate3d(${targetRect.left - curNewestDragItemDomRect.left
        }px,${targetRect.top - curNewestDragItemDomRect.top}px,0)`;

      setTimeout(() => {
        //排序
        sortPosition(curDragItem.id, targetItem.id);
      }, 200);
    }
  }

  // 获取指定id的组件元素
  const getDomItem = (id: any) => {
    return document.getElementsByName('item_' + id)?.[0] || {};
  };

  // 元素换位置
  const sortPosition = (sourceId: any, targetId: any) => {
    if (sourceId && targetId && targetId !== sourceId) {
      // 真正的节点交换顺序
      let tmpList = JSON.parse(JSON.stringify(list))
      let sourceItem = tmpList.find((val: any) => val.id == sourceId);
      let sourceItemIndex = tmpList.findIndex((val: any) => val.id == sourceId);
      let targetItem = tmpList.find((val: any) => val.id == targetId);
      let targetItemIndex = tmpList.findIndex((val: any) => val.id == targetId);
      tmpList.splice(sourceItemIndex, 1, targetItem);
      tmpList.splice(targetItemIndex, 1, sourceItem);
      setList(tmpList);

      // 清除动画及位移
      let sourceDom = getDomItem(sourceId);
      let targetDom = getDomItem(targetId);
      sourceDom.style.transition = '';
      sourceDom.style.transform = '';
      targetDom.style.transition = '';
      targetDom.style.transform = '';

      // 允许下次交换
      animationFlag = true;
    }
  }

  return (
    <div className={styles.wrap}>
      <ul
        className={styles.list}
        onDragOver={onDragOver}
      >
        {
          list.map((val: any) => {
            return (
              <li
                key={val.id}
                name={'item_' + val.id}
                className={styles.item}
                draggable={true}
                onDragStart={dragStart}
                onDragEnd={dragEnd}
                drag-data={JSON.stringify(val)}
              >
                {val.name}
              </li>
            )
          })
        }
      </ul>
    </div>
  )
}

.wrap{
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;

  .list{
    width: 480px;
    padding:10px;
  }

  .item{
    display: inline-flex;
    justify-content: center;
    align-items: center;
    height: 30px;
    width: 140px;
    margin-right: 20px;
    margin-bottom: 10px;
    border: 1px solid #eee;
    border-radius: 4px;
    background:#eee;
    cursor: pointer;
    &:nth-child(3n){
      margin-right: 0;
    }
  }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值