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

基于React State调整拖放功能的Ghost Image显示问题

解决拖拽时Ghost Image显示错误的问题

你当前的问题根源在于:在dragStart事件触发时立刻从数组中移除了拖拽元素,导致DOM瞬间重新渲染。浏览器生成的ghost image是基于拖拽开始瞬间的DOM状态,但此时元素已经被移除,后续元素移位,就出现了ghost image显示成下一个元素的情况。

正确的做法是不要在拖拽开始时修改数组,而是通过状态标记拖拽中的元素,仅在UI上隐藏它,等拖拽完成后再重新排序数组。这样既能让页面上的拖拽元素消失,又能保证浏览器生成正确的ghost image。

修改后的完整代码

import styles from '../../styles/test.module.css'
import { useState } from 'react'

export default function TestDragDrop() {
    const [tiles, setTiles] = useState([
        { APPID: 1, APPLICATIONNAME: 'App 1' },
        { APPID: 2, APPLICATIONNAME: 'App 2' },
        { APPID: 3, APPLICATIONNAME: 'App 3' },
        { APPID: 4, APPLICATIONNAME: 'App 4' },
        { APPID: 5, APPLICATIONNAME: 'App 5' },
        { APPID: 6, APPLICATIONNAME: 'App 6' },
        { APPID: 7, APPLICATIONNAME: 'App 7' },
        { APPID: 8, APPLICATIONNAME: 'App 8' },
        { APPID: 9, APPLICATIONNAME: 'App 9' },
    ])
    const [appBeingDragged, setAppBeingDragged] = useState(null)
    const [startIndex, setStartIndex] = useState(null)
    const [draggingId, setDraggingId] = useState(null)
    const [endIndex, setEndIndex] = useState(null)

    const handleDragStart = (e, index) => {
        e.dataTransfer.setData('id', e.target.id)
        const thisAppId = parseInt(e.target.id)
        const draggedApp = tiles.find(tile => tile.APPID === thisAppId)
        setAppBeingDragged(draggedApp)
        setStartIndex(index)
        setDraggingId(thisAppId)
    }

    const handleDragEnter = (i) => {
        setEndIndex(i)
    }

    const handleDragLeave = () => {
        setEndIndex(null)
    }

    const handleDrop = (e) => {
        e.preventDefault()
        reorderTiles(startIndex, endIndex)
        setDraggingId(null)
    }

    const reorderTiles = (startIndex, endIndex) => {
        if (endIndex === null) return
        const newTiles = [...tiles]
        const [draggedItem] = newTiles.splice(startIndex, 1)
        newTiles.splice(endIndex, 0, draggedItem)
        setTiles(newTiles)
        setAppBeingDragged(null)
        setStartIndex(null)
        setEndIndex(null)
    }

    const cards = tiles.map((app, index) => {
        return (
            <div
                key={app.APPID}
                id={app.APPID}
                className={`${styles.card} ${draggingId === app.APPID ? styles.dragging : ''}`}
                draggable
                onDragStart={(e) => handleDragStart(e, index)}
                onDragOver={(e) => e.preventDefault()}
                onDragEnter={() => handleDragEnter(index)}
                onDragLeave={handleDragLeave}
                onDrop={(e) => handleDrop(e)}
            >
                {app.APPLICATIONNAME}
            </div>
        )
    })

    return (
        <div style={{ display: 'flex', flexFlow: 'row wrap', margin: '1rem 5rem' }}>
            {cards}
        </div>
    )
}

配套CSS修改(test.module.css)

在样式文件中添加拖拽元素的隐藏样式:

.dragging {
    opacity: 0;
    pointer-events: none;
}

关键改动说明

  1. 用APPID作为元素key:避免使用索引作为key,防止拖拽时DOM元素识别混乱
  2. 新增draggingId状态:仅在UI层面隐藏拖拽元素(设为透明),不修改原数组,保证浏览器能基于原始DOM生成正确的ghost image
  3. 拖拽完成后再排序数组:等drop事件触发后,再对数组进行剪切插入操作,此时DOM更新不会影响已经生成的ghost image
  4. 添加pointer-events: none:防止隐藏的拖拽元素干扰后续的拖拽事件触发

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 06:02:18