You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React无动画拖拽功能实现:Mac OS下异常回退动画问题解决咨询

解决Mac OS上React拖拽卡片的回退动画问题

我正在复刻Trello的核心拖拽功能,目前功能已实现,但在MacBook上出现一个异常:将卡片从一列拖拽到另一列后,被拖拽的卡片会出现回到起始位置的动画效果,但实际上卡片的位置已经成功变更(Windows电脑无此问题)。我尝试使用Event.preventDefault()但没有解决问题。

以下是我的实现代码:

Column.js

import React, {useState, useRef} from 'react';
import './Column.css';

function Column({data}) {
  const [list, setList] = useState(data);
  const [dragging, setDragging] = useState(false);
  const dragItem = useRef();
  const dragNode = useRef();

  const handleDragStart = (event, params) => {
    console.log('drag starting', params)
    dragItem.current = params;
    dragNode.current = event.target;
    dragNode.current.addEventListener('dragend', handleDragEnd)
    setTimeout(() => {
      setDragging(true);
    }, 0)
  }

  const handleDragEnter = (event, params) => {
    console.log('entering drag', params);
    const currentItem = dragItem.current;
    if(event.target !== dragNode.current) {
      console.log('TARGET IS NOT THE SAME')
      setList(oldList => {
        let newList = JSON.parse(JSON.stringify(oldList));
        newList[params.groupIndex].items.splice(params.itemIndex, 0, newList[currentItem.groupIndex].items.splice(currentItem.itemIndex,1)[0])
        dragItem.current = params
        return newList;
      })
    }
  }

  const handleDragEnd = () => {
    console.log('Ending Drag')
    setDragging(false);
    dragNode.current.removeEventListener('dragend', handleDragEnd)
    dragItem.current = null;
    dragNode.current = null;
  }

  const getStyles = (params) => {
    const currentItem = dragItem.current;
    if (currentItem.groupIndex === params.groupIndex && currentItem.itemIndex === params.itemIndex) {
      return 'current drag-and-drop-item';
    }
    return 'drag-and-drop-item'
  };

  return (
    <section className="drag-and-drop-container">
      <div className="drag-and-drop">
        {list.map((group, groupIndex) => (
          <div
            key={group.title}
            className="drag-and-drop-group"
            onDragEnter={dragging && !group.items.length ? (event) => handleDragEnter(event, {groupIndex, itemIndex:0}) : null }
          >
            <div className="drag-and-drop-group-title">{group.title}</div>
            {group.items.map((item, itemIndex) => (
              <div
                draggable
                onDragStart={(dragEvent) => {handleDragStart(dragEvent, {groupIndex, itemIndex})}}
                onDragEnter={dragging ? (event) => {handleDragEnter(event, {groupIndex, itemIndex})} : null }
                key={item}
                className={dragging ? getStyles({groupIndex, itemIndex}) : "drag-and-drop-item" }
              >
                {item}
              </div>
            ))}
          </div>
        ))}
      </div>
    </section>
  )
}

export default Column;

Column.css

@import url(./../../index.css);

.drag-and-drop{
  display: grid;
  gap: 0.5rem;
  width: 100%;
  height: 100%;
  grid-template-columns: repeat(auto-fill, 300px);
  background-color: var(--primary-color);
  padding: 0.5rem;
  align-items: start;
}

.drag-and-drop-group{
  background-color: var(--secondary-color-80);
  padding: 0.5rem;
}

.drag-and-drop-group-title{
  margin-bottom: 0.5rem;
  font-size: 1.2rem;
  font-family: 'Poppins';
}

.drag-and-drop-item {
  background-color: #FFFFFF;
  color: var(--primary-color);
  min-height: 150px;
}

.drag-and-drop-item:not(:last-of-type){
  margin-bottom: 0.5rem;
}

.current{
  background-color: var(--primary-color);
}

请问如何解决Mac OS上的这个异常动画问题?


解决方案

这个问题是Mac OS上WebKit内核的默认拖拽行为导致的——它会在拖拽结束时对原始元素播放一个"回弹"动画,即使你已经修改了DOM结构。以下是几个可以解决这个问题的方法:

1. 在dragstart事件中禁用默认行为并设置拖拽数据

Mac的WebKit对拖拽事件有特殊处理,如果你没有设置拖拽数据,它会使用默认的元素拖拽行为,从而产生回弹动画。在handleDragStart里添加这两行:

const handleDragStart = (event, params) => {
  console.log('drag starting', params)
  event.preventDefault(); // 先阻止默认行为
  event.dataTransfer.setData('text/plain', ''); // 设置空拖拽数据,覆盖默认行为
  // 其余原有代码...
}

这能直接覆盖Mac的默认拖拽反馈,阻止回弹动画触发,改动最小且效果明显。

2. 优化拖拽时的原始元素隐藏逻辑

你的current类只是改变了背景色,但原始元素在拖拽时仍然留在DOM中,Mac的动画会作用于它。可以修改样式让拖拽中的原始元素完全隐藏:

.current {
  background-color: var(--primary-color);
  opacity: 0; /* 完全隐藏原始元素,避免动画被用户看到 */
  pointer-events: none; /* 防止干扰其他拖拽事件 */
}

这样拖拽时用户看不到原始元素,自然也就察觉不到回弹动画了。

3. 补充dragleave事件处理

有时候Mac上dragEnter的触发时机有延迟,导致状态更新不及时。可以给拖拽分组容器添加onDragLeave事件,确保拖拽状态同步:

<div
  key={group.title}
  className="drag-and-drop-group"
  onDragEnter={dragging && !group.items.length ? (event) => handleDragEnter(event, {groupIndex, itemIndex:0}) : null }
  onDragLeave={(event) => event.preventDefault()}
>

我建议先尝试第一种方法,如果还是有问题,再结合第二种方法隐藏原始元素,基本就能解决这个回弹动画的问题了。


内容的提问来源于stack exchange,提问作者Reeh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.01 02:39:09