React-Draggable 是一个 React 生态里用于创建可拖动元素的工具库,其使用及其简单,本文就来介绍。
创建项目
使用 Vite 的 React 模板创建项目:
npm create vite@latest react-draggable-demo -- --template=react
cd react-draggable-demo
# 使用 VS Code 打开
code .
安装依赖,并启动项目:
npm install
npm run dev
安装 React-Draggable
npm install react-draggable
删除 src/index.css 中的内容,修改 src/App.jsx:
import Draggable from 'react-draggable'
function App() {
return (
<>
<Draggable>
<div style={{ display: 'inline-flex', padding: '10px', border: '1px solid black', cursor: 'move' }}>I can now be moved around!</div>
</Draggable>
</>
)
}
export default App
效果如下:
就这样,我们就非常快速的创建了一个可拖动元素。
作用原理
React-Draggable 的拖动实现原理很简单,就是通过给子元素添加 transform: translate(0px, 0px) 实现的。
当然,还添加了一个默认的类名 .react-draggable,目前只是个标注,并没有设置样式,并无作用,忽略即可。
当我们执行拖动时,React-Draggable 会根据当前的拖动调整子元素的 transform 值,从而实现元素在横向、纵向上的移动。
根据 transform 移动元素有一个好处——不过你的子元素定位形式如何(相对、绝对说是默认的静态)都能正确移动。
这样呢,就会带来一个问题:如果拖动元素本身已经应用了 CSS transform,那么就会被 <Draggable> 覆盖。比如下面这样:
<Draggable>
<div style={{ display: 'inline-flex', padding: '10px', border: '1px solid black', cursor: 'move', transform: 'translate(50px, 50px)' }}>I can now be moved around!</div>
</Draggable>
然而展示的效果是下面这样:
transform: 'translate(50px, 50px)' 被覆盖成 transform: 'translate(0px, 0px)'。
为了避免覆盖,这个时候就要借助一个中间元素了,类似下面这样:
<Draggable>
<span>
...
</span>
</Draggable>
渲染效果如下:
发现拖放元素进行了正确的偏移。
常用功能
以上只是简单介绍了 <Draggable> 的使用,其组件上还有很多 props 用于设置其他拖放相关的表现。下面就来介绍比较常用的功能。
axis:指定为单轴拖动
<Draggable> 创建出来的拖动元素默认可以按照 X 或 Y 轴上移动。如果你只希望元素只能按照某个方向上拖动,那么可以借助 axis prop。
axis prop 有 2 个可以设置的值:x 和 y。
当设置成 y 时,那么只能在垂直方向上拖动。
- <Draggable>
+ <Draggable axis='y'>
效果:
当设置成 x 时,那么只能在水平方向上拖动。
- <Draggable>
+ <Draggable axis='x'>
效果:
handle:指定拖动源
被 <Draggable> 包裹的元素默认情况下,整个都是一个拖动源,但某些情况下,我们只希望为包裹元素的局部元素开发拖放能力——大家可以想一想浏览器顶部 Bar。
这一点 <Draggable> 也能帮我们做到——通过 handle prop。
<Draggable handle=".handle">
<div style={{ display: 'inline-flex', flexDirection: 'column' }}>
<div className="handle" style={{ cursor: 'move', padding: '10px', border: '1px solid' }}>Drag from here</div>
<div style={{ padding: '20px', border: '1px solid', marginTop: '-1px' }}>This readme is really dragging on...</div>
</div>
</Draggable>
handle prop 的值是一个 CSS selector 字符串,指定内部的某个元素作为拖放源。以上面代码为例,我们设定内部的 <div className="handle"> 作为我们的拖放源。最终效果如下:
会发现拖动内容区无效,只有头部元素是可拖放的。当然了,最终的拖放 transform 变动还是应用在最外层的 div 元素的(而非 .handle 元素)。
bounds: 限定拖放边界
<Draggable> 还有 1 个 bounds prop,可以指定可拖放的范围。
你可以将 bounds 设定 "parent" 。
<div style={{ position: 'relative', height: '50vh', width: '50vw', border: '1px solid' }}>
<Draggable
handle=".handle"
bounds="parent"
>...</Draggable>
</div>
"parent" 表示只能在父元素范围内移动。
注意,父元素上的 position: 'relative' 必须要设定,否则会被外部 body 默认的 8px 外边距影响,产生偏差。
当然,bounds 还可以设置成是一个 CSS Selector 字符串 。
<div className='drag-area' style={{ position: 'fixed', inset: 0, border: '2px solid red' }}></div>
<Draggable
handle=".handle"
bounds=".drag-area"
>
{/* ... */}
</Draggable>
以上,我们将 <Draggable> 可以拖动的区域限定在 .drag-area 元素范围内,这个元素的范围正好覆盖整个视口区域内。
这样我们将能讲拖放元素限定在整个能看到的范围内。
注册拖放事件
<Draggable> 有 3 个可供注册的拖放事件,分别是 onStart、onDrag 和 onStop。
const handleStart = (event, data) => {
console.log('Drag started', event, data)
}
const handleDrag = (event, data) => {
console.log('Dragging', data)
}
const handleStop = (event, data) => {
console.log('Drag stopped', event, data)
}
<Draggable
handle=".handle"
onStart={handleStart}
onDrag={handleDrag}
onStop={handleStop}
>
{/* ... */}
</Draggable>
我们来看操作效果:
onStart 在点击时触发,onDrag 在拖动时触发,onStop 在松开鼠标时触发。
事件处理函数中,第一个 MouseEvent 参数很少用,主要还是第二个 data 参数比较有用。
type DraggableEventHandler = (e: Event, data: DraggableData) => void | false;
type DraggableData = {
node: HTMLElement,
// lastX + deltaX === x
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number
};
类似下面这样:
里面包含当前元素的迁移量 x, y。
这个 x, y 就可以作为下次的初始迁移量使用,初始迁移量可以通过 defaultPosition prop 设定。
<Draggable
defaultPosition={{x: 82, y: 15}}
>...</Draggable>
效果:
其他可供使用 Props API 可以参考官方文档。
其他
不过在你使用 React-Draggable 的过程中,会在控制台看到一些告警信息。
这块 API 文档里也有说明:
// If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.
// Unfortunately, in order for <Draggable> to work properly, we need raw access
// to the underlying DOM node. If you want to avoid the warning, pass a `nodeRef`
// as in this example:
//
// function MyComponent() {
// const nodeRef = React.useRef(null);
// return (
// <Draggable nodeRef={nodeRef}>
// <div ref={nodeRef}>Example Target</div>
// </Draggable>
// );
// }
我们按照说明,只要引入一个 nodeRef,从 <Draggable> 拿到,再给到内部 <div> 即可消除。
import { useRef } from 'react';
import Draggable from 'react-draggable'
function App() {
const nodeRef = useRef(null);
return (
<>
<div className='drag-area' style={{ position: 'fixed', inset: 0, border: '2px solid red' }}></div>
<Draggable
handle=".handle"
bounds=".drag-area"
nodeRef={nodeRef}
>
<div ref={nodeRef} style={{ display: 'inline-flex', flexDirection: 'column' }}>
<div className="handle" style={{ cursor: 'move', padding: '10px', border: '1px solid' }}>Drag from here</div>
<div style={{ padding: '20px', border: '1px solid', marginTop: '-1px' }}>This readme is really dragging on...</div>
</div>
</Draggable>
</>
)
}
export default App
再来看看控制台,就看不见告警信息了。
总结
本文介绍了 React-Draggable 拖放库的使用,介绍了其简单使用、作用机制以及常用功能。
希望本文对你的工作能有所帮助,感谢阅读,再见。