目录:
我过去实现拖拽功能时一直使用原生 JavaScript,虽然之前了解过 @dnd-kit/core,但总觉得它过于复杂。最近发现社区推出了一个新工具——@dnd-kit/react,尝试之后发现,在 React 项目中用它来实现窗口拖拽或元素排序简直太方便了。
安装依赖:
$ npm install @dnd-kit/react
useDraggable 钩子用于创建一个可拖动的元素。
import { useDraggable } from '@dnd-kit/react';
function Draggable() {
const { ref } = useDraggable({
id: 'draggable',
})
return (
<div ref={ref}>
Draggable
</div>
)
}
你可能会问:给元素直接设置 draggable=”true” 属性不也能拖吗?两者有何区别?
实际上,draggable=”true” 主要是为浏览器原生的文件拖放和文本拖动而设计,拖动时原始元素会留在原地,光标处显示的是一个半透明的“副本”。
而 useDraggable 则提供了更贴近交互设计的能力,比如它可以让你“拽着”元素本身移动,并轻松实现单方向拖动、限制边界、添加弹性动画等高级效果。
与拖动对应的是放置。useDroppable 钩子用于创建一个可以接收被拖动元素的区域。
import { useDroppable } from '@dnd-kit/react';
function Droppable({ id, children }) {
const { ref } = useDroppable({
id,
})
return (
<div ref={ref}>
{children}
</div>
)
}
将两者结合,可以构建一个简单的拖放应用:
import React, { useState } from 'react'
import { DragDropProvider, useDraggable, useDroppable } from '@dnd-kit/react'
function Draggable() {
const { ref } = useDraggable({ id: 'draggable' })
return <div ref={ref}>Draggable</div>
}
function Droppable({ id, children }) {
const { ref } = useDroppable({ id })
return <div ref={ref}>{children}</div>
}
function App() {
const [isDropped, setIsDropped] = useState(false)
return (
<DragDropProvider
onDragEnd={(event) => {
if (event.canceled) return;
const { target } = event.operation;
setIsDropped(target?.id === 'droppable');
}}
>
{!isDropped && <Draggable />}
<Droppable id="droppable">
{isDropped && <span>已放入</span>}
</Droppable>
</DragDropProvider>
)
}
export default App
开发时经常遇到如下场景: 你不想让整个卡片都能被拖动,而只希望用户通过点击卡片角落的“把手”(如六个点图标)来触发拖动。这时,handleRef 就能派上用场。
如下示例: 点击 “Draggable” 文本是不会拖动的,只有点击 “只有点我才能拖动” 才会拖动。
function Draggable() {
const { ref, handleRef } = useDraggable({
id: 'draggable',
})
return (
<div ref={ref}>
Draggable
<span ref={handleRef} style={{ color: 'red' }}>
只有点我才能拖动
</span>
</div>
)
}
modifiers 让你能精细地控制拖动的物理行为,例如限制只能在水平或垂直方向移动,或禁止元素被拖出某个容器的边界:
import { useDraggable } from '@dnd-kit/react';
import { RestrictToElement } from '@dnd-kit/dom/modifiers';
function Draggable({ id }) {
const { ref } = useDraggable({
id,
modifiers: [RestrictToElement.configure({ element: document.body })], // 限制容器拖出 document.body 边界
})
}
useDroppable 返回的 isDropTarget 是一个布尔值,用于指示当前是否有可拖曳元素拖到了可放置区域上方,通常会加样式提升交互反馈。
如下示例: 当可拖曳元素拖到可放置区域上方,此时还没松开鼠标,给可放置区域添加 ‘active-droppable’ 类名,比如加个边框。
function Droppable({ id, children }) {
const { ref, isDropTarget } = useDroppable({ id });
return (
<div
ref={ref}
className={isDropTarget ? 'active-droppable' : 'droppable'}
>
{children}
</div>
)
}
排序是拖拽的常见场景。如果使用传统的 @dnd-kit/core 来实现,还需要额外安装 @dnd-kit/sortable 库,使用 @dnd-kit/react 是不需要额外安装库的,自身已经携带了排序功能。
import { useState } from 'react';
import { DragDropProvider } from '@dnd-kit/react';
import { useSortable } from '@dnd-kit/react/sortable';
import { move } from '@dnd-kit/helpers';
function SortableItem({ id, index }: { id: string, index: number }) {
const { ref, isDragging } = useSortable({ id, index });
return (
<div
ref={ref}
style={{ opacity: isDragging ? 0.5 : 1 }}
>
项目 {id}
</div>
)
}
export function App() {
const [items, setItems] = useState([
{ id: '1', name: '项目 1' },
{ id: '2', name: '项目 2' },
{ id: '3', name: '项目 3' },
{ id: '4', name: '项目 4' },
{ id: '5', name: '项目 5' },
])
console.log('items', items)
return (
<DragDropProvider
onDragEnd={(event) => {
setItems((currentItems) => move(currentItems, event));
}}
>
<div>
{items.map((item, index) => (
<SortableItem key={item.id} id={item.id} index={index} />
))}
</div>
</DragDropProvider>
);
}
export default App
↶ 返回首页 ↶