React拖拽时保持被拖拽元素尺寸不变的实现方案
问题:拖拽排序时元素尺寸被拉伸/收缩
我用React开发了一个支持拖拽排序句子的应用,现在遇到一个问题:小尺寸的拖拽元素经过大尺寸元素时会被拉伸,大尺寸元素经过小尺寸元素时会被收缩。需要在拖拽过程中保持被拖拽元素的原始尺寸。
附上现有代码:
App.jsx
import React, { useState, useEffect } from 'react'; import { DndContext, closestCenter,useTransform } from '@dnd-kit/core'; import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import 'bootstrap/dist/css/bootstrap.min.css'; import { SortableItem } from './SortableItem.jsx'; import './App.css'; function App() { const [ranking, setRanking] = useState([ "I dont understand why people dont accept that no matter what,Boys will be boys and men will be men", "She is such a drama queen.", "Don't be a sissy.", "Man is a better driver than women.", "God is a man." ]); function handleDragEnd(event) { console.log('Drag'); const { active, over } = event; console.log('Active:' + active.id); console.log('Over :' + over.id); if (active.id !== over.id) { setRanking((items) => { const activeIndex = items.indexOf(active.id); const overIndex = items.indexOf(over.id); console.log(arrayMove(items, activeIndex, overIndex)); return arrayMove(items, activeIndex, overIndex); }); } } return ( <div> <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd} > <h3 style={{ color: 'black', fontWeight: 'bold' }} align="center"> Rank the following sentences from most to least sexist. </h3> <div className="frame"> <SortableContext items={ranking} strategy={verticalListSortingStrategy}> {ranking.map((rank) => ( <SortableItem key={rank} id={rank} className="sortable-item" /> ))} </SortableContext> <div className="button" style={{ display: 'flex', justifyContent: 'center' }}> <button className="btn submit-button" align="center" onClick={(e) => { e.preventDefault(); console.log(ranking); }} > Submit </button> </div> </div> </DndContext> </div> ); } export default App;
SortableItem.jsx
import React from "react"; import Card from 'react-bootstrap/Card'; import { useSortable } from "@dnd-kit/sortable"; import {CSS} from "@dnd-kit/utilities"; export function SortableItem(props){ //props.id const { attributes, listeners, setNodeRef, transform, transition } = useSortable({id: props.id}); const style = { transform: CSS.Transform.toString(transform), transition, marginBottom: "1rem", }; return ( <div ref={setNodeRef} style={style} {...attributes} {...listeners} className="hover-card" > <Card body>{props.id}</Card> </div> ); }
我尝试过useTransform、useRef、设置卡片尺寸和响应式CSS,但效果都不符合预期,希望得到解决方法。
解决方案
这个问题的核心是@dnd-kit默认在拖拽时会用占位符元素替换原位置,占位符会继承目标位置的尺寸,导致拖拽元素变形。要解决这个问题,我们可以使用DragOverlay组件渲染独立的、保持原始尺寸的拖拽元素。
步骤1:修改App.jsx,添加DragOverlay
在DndContext内部引入DragOverlay,并管理当前激活的拖拽元素:
import React, { useState } from 'react'; import { DndContext, closestCenter, DragOverlay } from '@dnd-kit/core'; import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import 'bootstrap/dist/css/bootstrap.min.css'; import { SortableItem } from './SortableItem.jsx'; import './App.css'; function App() { const [ranking, setRanking] = useState([ "I dont understand why people dont accept that no matter what,Boys will be boys and men will be men", "She is such a drama queen.", "Don't be a sissy.", "Man is a better driver than women.", "God is a man." ]); const [activeId, setActiveId] = useState(null); function handleDragStart(event) { setActiveId(event.active.id); } function handleDragEnd(event) { const { active, over } = event; setActiveId(null); if (active.id !== over.id) { setRanking((items) => { const activeIndex = items.indexOf(active.id); const overIndex = items.indexOf(over.id); return arrayMove(items, activeIndex, overIndex); }); } } return ( <div> <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd} onDragStart={handleDragStart} > <h3 style={{ color: 'black', fontWeight: 'bold' }} align="center"> Rank the following sentences from most to least sexist. </h3> <div className="frame"> <SortableContext items={ranking} strategy={verticalListSortingStrategy}> {ranking.map((rank) => ( <SortableItem key={rank} id={rank} className="sortable-item" /> ))} </SortableContext> <div className="button" style={{ display: 'flex', justifyContent: 'center' }}> <button className="btn submit-button" onClick={(e) => { e.preventDefault(); console.log(ranking); }} > Submit </button> </div> </div> {/* 渲染独立的拖拽元素 */} <DragOverlay> {activeId ? ( <div className="hover-card" style={{ pointerEvents: 'none' }}> <Card body>{activeId}</Card> </div> ) : null} </DragOverlay> </DndContext> </div> ); } export default App;
步骤2:修改SortableItem.jsx,隐藏拖拽中的原元素
拖拽时隐藏原元素,避免和克隆的拖拽元素重叠:
import React from "react"; import Card from 'react-bootstrap/Card'; import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; export function SortableItem(props){ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({id: props.id}); const style = { transform: CSS.Transform.toString(transform), transition, marginBottom: "1rem", opacity: isDragging ? 0 : 1, visibility: isDragging ? 'hidden' : 'visible' }; return ( <div ref={setNodeRef} style={style} {...attributes} {...listeners} className="hover-card" > <Card body>{props.id}</Card> </div> ); }
步骤3:添加CSS确保拖拽元素尺寸稳定
在App.css中添加样式,防止拖拽元素被容器拉伸:
.dndkit-drag-overlay { pointer-events: none; } .dndkit-drag-overlay .hover-card { width: auto; min-width: 200px; max-width: 90%; }
原理说明
DragOverlay渲染的拖拽元素独立于排序列表,不会继承目标位置的尺寸,完全保持原始元素的大小。- 拖拽时隐藏原元素,避免视觉重叠和交互冲突。
- CSS限制拖拽元素的宽高范围,进一步确保尺寸稳定。
内容的提问来源于stack exchange,提问作者Sitashma Rajbhandari
相关产品推荐
相关产品推荐

