@ohkit/draggable-box 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +5 -0
- package/README.md +718 -0
- package/dist/index.css +2 -0
- package/dist/index.css.map +1 -0
- package/dist/index.es.js +2 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.modern.mjs +2 -0
- package/dist/index.modern.mjs.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/index.d.ts +69 -0
- package/dist/types/type.d.ts +67 -0
- package/dist/types/utils.d.ts +18 -0
- package/package.json +38 -0
- package/src/constants.ts +2 -0
- package/src/index.tsx +536 -0
- package/src/style.scss +18 -0
- package/src/type.ts +68 -0
- package/src/utils.ts +57 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Copyright (c) [2025], [wuqiuyang]
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
4
|
+
|
|
5
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
# @ohkit/draggable-box
|
|
2
|
+
|
|
3
|
+
🚀 功能强大的可拖拽容器组件
|
|
4
|
+
|
|
5
|
+
一个灵活的拖拽容器组件,支持方向锁定、边界限制、多种定位模式和 Transform 缩放支持,适用于悬浮窗口、工具栏、侧边栏等各种需要拖拽交互的场景。
|
|
6
|
+
|
|
7
|
+
## ✨ 特性亮点
|
|
8
|
+
|
|
9
|
+
- 🎯 **灵活拖拽**: 支持窗口范围内的自由拖拽
|
|
10
|
+
- 🔒 **方向锁定**: 可锁定水平或垂直方向拖动
|
|
11
|
+
- 🎚️ **边界控制**: 智能边界限制,支持相对和绝对边界
|
|
12
|
+
- 📐 **多种定位**: 支持四角定位,适应不同布局需求
|
|
13
|
+
- 🔄 **定位模式**: 支持 fixed 和 absolute 两种定位模式
|
|
14
|
+
- 📏 **Transform 支持**: 自动适应父容器的 transform 缩放
|
|
15
|
+
- ♿ **可访问性**: 支持禁用状态,提升用户体验
|
|
16
|
+
- 🎨 **样式自定义**: 支持自定义类名和样式覆盖
|
|
17
|
+
- 📱 **响应式**: 自适应窗口大小变化
|
|
18
|
+
|
|
19
|
+
## 📦 安装
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @ohkit/draggable-box
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🚀 快速开始
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import React from 'react';
|
|
29
|
+
import { DraggableBox } from '@ohkit/draggable-box';
|
|
30
|
+
import '@ohkit/draggable-box/dist/index.css';
|
|
31
|
+
|
|
32
|
+
function App() {
|
|
33
|
+
return (
|
|
34
|
+
<div style={{ height: '100vh', position: 'relative' }}>
|
|
35
|
+
{/* 基本用法 - 自由拖拽 */}
|
|
36
|
+
<DraggableBox>
|
|
37
|
+
<div style={{ padding: '20px', background: '#f0f0f0', borderRadius: '8px' }}>
|
|
38
|
+
我可以自由拖拽!
|
|
39
|
+
</div>
|
|
40
|
+
</DraggableBox>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 📖 详细使用
|
|
47
|
+
|
|
48
|
+
### 方向锁定
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
function App() {
|
|
52
|
+
return (
|
|
53
|
+
<div style={{ height: '100vh', position: 'relative' }}>
|
|
54
|
+
{/* 锁定水平拖拽 */}
|
|
55
|
+
<DraggableBox lockAxis="x" placement="top-left">
|
|
56
|
+
<div style={{ padding: '16px', background: '#e3f2fd', borderRadius: '6px' }}>
|
|
57
|
+
我只能水平拖拽
|
|
58
|
+
</div>
|
|
59
|
+
</DraggableBox>
|
|
60
|
+
|
|
61
|
+
{/* 锁定垂直拖拽 */}
|
|
62
|
+
<DraggableBox lockAxis="y" placement="top-right">
|
|
63
|
+
<div style={{ padding: '16px', background: '#f3e5f5', borderRadius: '6px' }}>
|
|
64
|
+
我只能垂直拖拽
|
|
65
|
+
</div>
|
|
66
|
+
</DraggableBox>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 边界限制
|
|
73
|
+
|
|
74
|
+
边界限制基于 `placement` 属性进行相对定位,智能计算有效拖动范围:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
function App() {
|
|
78
|
+
return (
|
|
79
|
+
<div style={{ height: '100vh', position: 'relative' }}>
|
|
80
|
+
{/* 左上角相对边界 */}
|
|
81
|
+
<DraggableBox
|
|
82
|
+
placement="top-left"
|
|
83
|
+
offsetX={50}
|
|
84
|
+
offsetY={50}
|
|
85
|
+
boundsX={[20, 300]} // 左边最小20px,最大300px
|
|
86
|
+
boundsY={[20, 200]} // 顶边最小20px,最大200px
|
|
87
|
+
>
|
|
88
|
+
<div style={{ padding: '12px', background: '#fff3e0' }}>
|
|
89
|
+
左上角边界限制
|
|
90
|
+
</div>
|
|
91
|
+
</DraggableBox>
|
|
92
|
+
|
|
93
|
+
{/* 右下角相对边界 */}
|
|
94
|
+
<DraggableBox
|
|
95
|
+
placement="bottom-right"
|
|
96
|
+
offsetX={50}
|
|
97
|
+
offsetY={50}
|
|
98
|
+
boundsX={[100, 500]} // 右边最小100px,最大500px
|
|
99
|
+
boundsY={[50, 300]} // 底边最小50px,最大300px
|
|
100
|
+
>
|
|
101
|
+
<div style={{ padding: '12px', background: '#e8f5e8' }}>
|
|
102
|
+
右下角边界限制
|
|
103
|
+
</div>
|
|
104
|
+
</DraggableBox>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 定位模式
|
|
111
|
+
|
|
112
|
+
组件支持两种定位模式,适应不同的使用场景:
|
|
113
|
+
|
|
114
|
+
#### Fixed 模式(默认)
|
|
115
|
+
|
|
116
|
+
适用于大多数场景,自动适应父容器的 transform 属性:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
function FixedModeExample() {
|
|
120
|
+
return (
|
|
121
|
+
<div style={{ height: '100vh', transform: 'scale(0.8)' }}>
|
|
122
|
+
{/* 在有 transform 的父容器中正常工作 */}
|
|
123
|
+
<DraggableBox positionMode="fixed">
|
|
124
|
+
<div style={{ padding: '16px', background: '#e3f2fd' }}>
|
|
125
|
+
Fixed 模式 - 自动适应 transform
|
|
126
|
+
</div>
|
|
127
|
+
</DraggableBox>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### Absolute 模式
|
|
134
|
+
|
|
135
|
+
适用于需要在定位容器内拖拽的场景:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
function AbsoluteModeExample() {
|
|
139
|
+
return (
|
|
140
|
+
<div style={{ position: 'relative', width: '600px', height: '400px', border: '2px solid #52c41a' }}>
|
|
141
|
+
{/* 在定位容器内拖拽 */}
|
|
142
|
+
<DraggableBox positionMode="absolute" placement="top-left">
|
|
143
|
+
<div style={{ padding: '16px', background: '#f6ffed' }}>
|
|
144
|
+
Absolute 模式 - 在定位容器内
|
|
145
|
+
</div>
|
|
146
|
+
</DraggableBox>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 拖拽区域可视化
|
|
153
|
+
|
|
154
|
+
启用 `showDragArea` 可以在拖拽时显示可拖拽范围:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
function DragAreaExample() {
|
|
158
|
+
return (
|
|
159
|
+
<DraggableBox
|
|
160
|
+
placement="bottom-right"
|
|
161
|
+
boundsX={[50, 300]}
|
|
162
|
+
boundsY={[50, 200]}
|
|
163
|
+
showDragArea={true}
|
|
164
|
+
>
|
|
165
|
+
<div style={{ padding: '16px', background: '#fff7e6' }}>
|
|
166
|
+
拖拽时显示可拖拽区域
|
|
167
|
+
</div>
|
|
168
|
+
</DraggableBox>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 组合功能
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
function App() {
|
|
177
|
+
return (
|
|
178
|
+
<div style={{ height: '100vh', position: 'relative' }}>
|
|
179
|
+
{/* 水平锁定 + 边界限制 */}
|
|
180
|
+
<DraggableBox
|
|
181
|
+
lockAxis="x"
|
|
182
|
+
placement="top-left"
|
|
183
|
+
boundsX={[50, 400]}
|
|
184
|
+
zIndex={10000}
|
|
185
|
+
>
|
|
186
|
+
<div style={{
|
|
187
|
+
padding: '15px',
|
|
188
|
+
background: 'linear-gradient(45deg, #ff6b6b, #feca57)',
|
|
189
|
+
color: 'white',
|
|
190
|
+
borderRadius: '8px'
|
|
191
|
+
}}>
|
|
192
|
+
水平锁定 + 边界限制
|
|
193
|
+
</div>
|
|
194
|
+
</DraggableBox>
|
|
195
|
+
|
|
196
|
+
{/* 禁用状态 */}
|
|
197
|
+
<DraggableBox
|
|
198
|
+
placement="bottom-right"
|
|
199
|
+
disabled={true}
|
|
200
|
+
>
|
|
201
|
+
<div style={{ padding: '12px', background: '#bdbdbd', color: '#666' }}>
|
|
202
|
+
禁用的拖拽框
|
|
203
|
+
</div>
|
|
204
|
+
</DraggableBox>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 高级用法 - 浮动工具栏
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
function FloatingToolbar() {
|
|
214
|
+
const [visible, setVisible] = useState(true);
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<DraggableBox
|
|
218
|
+
placement="top-right"
|
|
219
|
+
offsetX={20}
|
|
220
|
+
offsetY={100}
|
|
221
|
+
boundsX={[10, 500]}
|
|
222
|
+
boundsY={[50, window.innerHeight - 200]}
|
|
223
|
+
zIndex={9999}
|
|
224
|
+
>
|
|
225
|
+
<div style={{
|
|
226
|
+
padding: '10px 15px',
|
|
227
|
+
background: 'white',
|
|
228
|
+
borderRadius: '25px',
|
|
229
|
+
boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
|
|
230
|
+
border: '1px solid #e0e0e0'
|
|
231
|
+
}}>
|
|
232
|
+
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
|
233
|
+
<button onClick={() => setVisible(!visible)}>
|
|
234
|
+
{visible ? '隐藏' : '显示'}
|
|
235
|
+
</button>
|
|
236
|
+
<span>浮动工具栏</span>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</DraggableBox>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Transform 缩放支持
|
|
245
|
+
|
|
246
|
+
组件自动适应父容器的 transform 缩放,无需额外配置:
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
function TransformExample() {
|
|
250
|
+
return (
|
|
251
|
+
<div style={{
|
|
252
|
+
width: '600px',
|
|
253
|
+
height: '400px',
|
|
254
|
+
border: '2px solid #1890ff',
|
|
255
|
+
transform: 'scale(0.8)',
|
|
256
|
+
transformOrigin: 'top left',
|
|
257
|
+
padding: '20px',
|
|
258
|
+
background: '#f0f0f0'
|
|
259
|
+
}}>
|
|
260
|
+
<p style={{ marginBottom: '20px', color: '#666' }}>
|
|
261
|
+
父容器有 transform: scale(0.8),组件自动适应
|
|
262
|
+
</p>
|
|
263
|
+
<DraggableBox
|
|
264
|
+
placement="bottom-right"
|
|
265
|
+
offsetX={50}
|
|
266
|
+
offsetY={50}
|
|
267
|
+
boundsX={[20, 300]}
|
|
268
|
+
boundsY={[20, 200]}
|
|
269
|
+
showDragArea={true}
|
|
270
|
+
>
|
|
271
|
+
<div style={{ padding: '16px', background: '#e6f7ff' }}>
|
|
272
|
+
在 transform 父元素中正常拖拽
|
|
273
|
+
</div>
|
|
274
|
+
</DraggableBox>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 📋 API 参考
|
|
281
|
+
|
|
282
|
+
### DraggableBoxProps
|
|
283
|
+
|
|
284
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
285
|
+
|------|------|--------|------|
|
|
286
|
+
| `className` | `string` | `''` | 自定义 CSS 类名 |
|
|
287
|
+
| `children` | `ReactNode` | - | 拖拽框内容 |
|
|
288
|
+
| `zIndex` | `number` | `9999` | z-index 层级 |
|
|
289
|
+
| `offsetX` | `number` | `20` | 初始位置横向偏移量(px) |
|
|
290
|
+
| `offsetY` | `number` | `20` | 初始位置纵向偏移量(px) |
|
|
291
|
+
| `disabled` | `boolean` | `false` | 是否禁用拖拽功能 |
|
|
292
|
+
| `placement` | `'top-left'` \| `'top-right'` \| `'bottom-left'` \| `'bottom-right'` | `'bottom-right'` | 初始定位位置 |
|
|
293
|
+
| `lockAxis` | `'none'` \| `'x'` \| `'y'` | `'none'` | 拖拽方向锁定 |
|
|
294
|
+
| `boundsX` | `[number?, number?]` | - | X轴边界范围 `[min, max]` |
|
|
295
|
+
| `boundsY` | `[number?, number?]` | - | Y轴边界范围 `[min, max]` |
|
|
296
|
+
| `positionMode` | `'fixed'` \| `'absolute'` | `'fixed'` | 定位模式 |
|
|
297
|
+
| `showDragArea` | `boolean` | `false` | 是否显示拖拽区域可视化 |
|
|
298
|
+
|
|
299
|
+
### 💡 定位模式说明
|
|
300
|
+
|
|
301
|
+
#### `positionMode="fixed"`(默认)
|
|
302
|
+
|
|
303
|
+
- 使用 `position: fixed` 定位
|
|
304
|
+
- 自动查找影响 fixed 定位的父元素(transform/filter/perspective)
|
|
305
|
+
- 适用于大多数场景,特别是父容器有 transform 时
|
|
306
|
+
- 组件会自动适应父容器的缩放
|
|
307
|
+
|
|
308
|
+
#### `positionMode="absolute"`
|
|
309
|
+
|
|
310
|
+
- 使用 `position: absolute` 定位
|
|
311
|
+
- 基于最近的非 static 定位父元素
|
|
312
|
+
- 适用于需要在特定定位容器内拖拽的场景
|
|
313
|
+
- 父容器需要有 `position: relative`、`absolute` 或 `fixed`
|
|
314
|
+
|
|
315
|
+
### 💡 边界说明
|
|
316
|
+
|
|
317
|
+
边界值基于 `placement` 属性进行智能计算:
|
|
318
|
+
|
|
319
|
+
#### 单边边界约束
|
|
320
|
+
|
|
321
|
+
除了完整的边界范围,DraggableBox 还支持单边边界约束,可以只设置最小边界或最大边界:
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
// 只有最小边界约束
|
|
325
|
+
<DraggableBox
|
|
326
|
+
placement="bottom-right"
|
|
327
|
+
boundsX={[100, undefined]} // 右边最小距离100px,无最大限制
|
|
328
|
+
boundsY={[50, undefined]} // 底边最小距离50px,无最大限制
|
|
329
|
+
>
|
|
330
|
+
单边最小边界约束
|
|
331
|
+
</DraggableBox>
|
|
332
|
+
|
|
333
|
+
// 只有最大边界约束
|
|
334
|
+
<DraggableBox
|
|
335
|
+
placement="top-left"
|
|
336
|
+
boundsX={[undefined, 200]} // 左边最大距离200px,无最小限制
|
|
337
|
+
boundsY={[undefined, 150]} // 顶边最大距离150px,无最小限制
|
|
338
|
+
>
|
|
339
|
+
单边最大边界约束
|
|
340
|
+
</DraggableBox>
|
|
341
|
+
|
|
342
|
+
// 混合单边约束
|
|
343
|
+
<DraggableBox
|
|
344
|
+
placement="bottom-right"
|
|
345
|
+
boundsX={[100, undefined]} // X轴只有最小边界
|
|
346
|
+
boundsY={[undefined, 30]} // Y轴只有最大边界
|
|
347
|
+
>
|
|
348
|
+
混合单边约束
|
|
349
|
+
</DraggableBox>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### 当 `placement="top-left"` 时:
|
|
353
|
+
|
|
354
|
+
- `boundsX=[min, max]`: 左边最小距离 - 左边最大距离
|
|
355
|
+
- `boundsY=[min, max]`: 顶边最小距离 - 顶边最大距离
|
|
356
|
+
|
|
357
|
+
#### 当 `placement="bottom-right"` 时:
|
|
358
|
+
|
|
359
|
+
- `boundsX=[min, max]`: 右边最小距离 - 右边最大距离
|
|
360
|
+
- `boundsY=[min, max]`: 底边最小距离 - 底边最大距离
|
|
361
|
+
|
|
362
|
+
#### 当 `placement="top-right"` 时:
|
|
363
|
+
|
|
364
|
+
- `boundsX=[min, max]`: 右边最小距离 - 右边最大距离
|
|
365
|
+
- `boundsY=[min, max]`: 顶边最小距离 - 顶边最大距离
|
|
366
|
+
|
|
367
|
+
#### 当 `placement="bottom-left"` 时:
|
|
368
|
+
|
|
369
|
+
- `boundsX=[min, max]`: 左边最小距离 - 左边最大距离
|
|
370
|
+
- `boundsY=[min, max]`: 底边最小距离 - 底边最大距离
|
|
371
|
+
|
|
372
|
+
## 💡 最佳实践
|
|
373
|
+
|
|
374
|
+
### 1. 边界配置建议
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
// ✅ 推荐:合理设置边界,确保用户体验
|
|
378
|
+
<DraggableBox
|
|
379
|
+
placement="top-right"
|
|
380
|
+
boundsX={[20, 400]} // 右边距离20px-400px
|
|
381
|
+
boundsY={[50, 300]} // 顶边距离50px-300px
|
|
382
|
+
>
|
|
383
|
+
优化边界配置
|
|
384
|
+
</DraggableBox>
|
|
385
|
+
|
|
386
|
+
// ❌ 避免:边界设置不合理
|
|
387
|
+
<DraggableBox boundsX={[0, 10]}>边界太窄</DraggableBox>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 2. 性能优化
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
// ✅ 推荐:合理的初始位置
|
|
394
|
+
<DraggableBox placement="bottom-right" offsetX={20} offsetY={20}>
|
|
395
|
+
合适的初始位置
|
|
396
|
+
</DraggableBox>
|
|
397
|
+
|
|
398
|
+
// ✅ 推荐:单边边界约束使用场景
|
|
399
|
+
<DraggableBox boundsX={[100, undefined]}>
|
|
400
|
+
{/* 确保组件不贴边,但又给予最大的移动自由 */}
|
|
401
|
+
</DraggableBox>
|
|
402
|
+
|
|
403
|
+
// ✅ 推荐:在需要时启用拖拽
|
|
404
|
+
<DraggableBox disabled={!isEditing}>
|
|
405
|
+
仅在编辑模式下可拖拽
|
|
406
|
+
</DraggableBox>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 3. 定位模式选择
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
// ✅ 推荐:大多数场景使用 fixed 模式
|
|
413
|
+
<DraggableBox positionMode="fixed">
|
|
414
|
+
{/* 自动适应父容器的 transform */}
|
|
415
|
+
</DraggableBox>
|
|
416
|
+
|
|
417
|
+
// ✅ 推荐:在定位容器内使用 absolute 模式
|
|
418
|
+
<div style={{ position: 'relative' }}>
|
|
419
|
+
<DraggableBox positionMode="absolute">
|
|
420
|
+
{/* 在定位容器内拖拽 */}
|
|
421
|
+
</DraggableBox>
|
|
422
|
+
</div>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### 4. Transform 缩放场景
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
// ✅ 推荐:在有 transform 的父容器中使用 fixed 模式
|
|
429
|
+
<div style={{ transform: 'scale(0.8)' }}>
|
|
430
|
+
<DraggableBox positionMode="fixed">
|
|
431
|
+
{/* 组件会自动适应缩放 */}
|
|
432
|
+
</DraggableBox>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
// ✅ 推荐:启用拖拽区域可视化以查看可拖拽范围
|
|
436
|
+
<DraggableBox
|
|
437
|
+
positionMode="fixed"
|
|
438
|
+
boundsX={[50, 300]}
|
|
439
|
+
boundsY={[50, 200]}
|
|
440
|
+
showDragArea={true}
|
|
441
|
+
>
|
|
442
|
+
{/* 拖拽时显示可拖拽区域 */}
|
|
443
|
+
</DraggableBox>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 5. 响应式设计
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
function ResponsiveDraggable() {
|
|
450
|
+
const [windowSize, setWindowSize] = useState({
|
|
451
|
+
width: window.innerWidth,
|
|
452
|
+
height: window.innerHeight
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
const handleResize = () => {
|
|
457
|
+
setWindowSize({
|
|
458
|
+
width: window.innerWidth,
|
|
459
|
+
height: window.innerHeight
|
|
460
|
+
});
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
window.addEventListener('resize', handleResize);
|
|
464
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
465
|
+
}, []);
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<DraggableBox
|
|
469
|
+
boundsX={[20, windowSize.width - 200]}
|
|
470
|
+
boundsY={[20, windowSize.height - 150]}
|
|
471
|
+
>
|
|
472
|
+
响应式拖拽框
|
|
473
|
+
</DraggableBox>
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
## 🌟 使用场景
|
|
479
|
+
|
|
480
|
+
### 浮动控制面板
|
|
481
|
+
|
|
482
|
+
```tsx
|
|
483
|
+
function ControlPanel() {
|
|
484
|
+
return (
|
|
485
|
+
<DraggableBox
|
|
486
|
+
placement="bottom-right"
|
|
487
|
+
offsetX={50}
|
|
488
|
+
offsetY={50}
|
|
489
|
+
boundsX={[50, 600]}
|
|
490
|
+
boundsY={[100, 500]}
|
|
491
|
+
zIndex={10000}
|
|
492
|
+
>
|
|
493
|
+
<div style={{
|
|
494
|
+
padding: '20px',
|
|
495
|
+
background: 'white',
|
|
496
|
+
borderRadius: '12px',
|
|
497
|
+
boxShadow: '0 8px 32px rgba(0,0,0,0.12)'
|
|
498
|
+
}}>
|
|
499
|
+
<h3>控制面板</h3>
|
|
500
|
+
{/* 控制项 */}
|
|
501
|
+
</div>
|
|
502
|
+
</DraggableBox>
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### 可拖拽侧边工具栏
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
function DraggableToolbar() {
|
|
511
|
+
return (
|
|
512
|
+
<DraggableBox
|
|
513
|
+
lockAxis="y"
|
|
514
|
+
placement="top-right"
|
|
515
|
+
offsetX={0}
|
|
516
|
+
offsetY={100}
|
|
517
|
+
boundsX={[0, 0]} // 锁定在右侧
|
|
518
|
+
boundsY={[50, 600]}
|
|
519
|
+
>
|
|
520
|
+
<div style={{
|
|
521
|
+
padding: '10px',
|
|
522
|
+
background: '#f8f9fa',
|
|
523
|
+
borderLeft: '1px solid #dee2e6',
|
|
524
|
+
width: '200px',
|
|
525
|
+
height: '300px'
|
|
526
|
+
}}>
|
|
527
|
+
<h4>侧边工具栏</h4>
|
|
528
|
+
{/* 工具栏按钮 */}
|
|
529
|
+
</div>
|
|
530
|
+
</DraggableBox>
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Transform 缩放场景
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
function ScaledContainer() {
|
|
539
|
+
return (
|
|
540
|
+
<div style={{
|
|
541
|
+
width: '800px',
|
|
542
|
+
height: '600px',
|
|
543
|
+
transform: 'scale(0.8)',
|
|
544
|
+
transformOrigin: 'top left',
|
|
545
|
+
border: '2px solid #1890ff',
|
|
546
|
+
padding: '20px',
|
|
547
|
+
background: '#f5f5f5'
|
|
548
|
+
}}>
|
|
549
|
+
<h3 style={{ marginBottom: '20px' }}>
|
|
550
|
+
缩放容器 (scale: 0.8)
|
|
551
|
+
</h3>
|
|
552
|
+
<DraggableBox
|
|
553
|
+
placement="bottom-right"
|
|
554
|
+
offsetX={50}
|
|
555
|
+
offsetY={50}
|
|
556
|
+
boundsX={[50, 500]}
|
|
557
|
+
boundsY={[50, 400]}
|
|
558
|
+
showDragArea={true}
|
|
559
|
+
>
|
|
560
|
+
<div style={{
|
|
561
|
+
padding: '16px',
|
|
562
|
+
background: 'white',
|
|
563
|
+
borderRadius: '8px',
|
|
564
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
|
565
|
+
}}>
|
|
566
|
+
在缩放容器中正常拖拽
|
|
567
|
+
</div>
|
|
568
|
+
</DraggableBox>
|
|
569
|
+
</div>
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## 🎨 自定义样式
|
|
575
|
+
|
|
576
|
+
组件支持通过 `className` 属性传入自定义样式类名,实现样式覆盖:
|
|
577
|
+
|
|
578
|
+
### 基础样式自定义
|
|
579
|
+
|
|
580
|
+
```tsx
|
|
581
|
+
function CustomStyledDraggable() {
|
|
582
|
+
return (
|
|
583
|
+
<DraggableBox className="my-custom-draggable">
|
|
584
|
+
<div>自定义样式的拖拽框</div>
|
|
585
|
+
</DraggableBox>
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
```css
|
|
591
|
+
/* 在您的 CSS 文件中 */
|
|
592
|
+
.my-custom-draggable {
|
|
593
|
+
/* 覆盖容器样式 */
|
|
594
|
+
cursor: grab;
|
|
595
|
+
border-radius: 12px;
|
|
596
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
|
597
|
+
border: 2px solid #e0e0e0;
|
|
598
|
+
transition: box-shadow 0.2s ease;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.my-custom-draggable:hover {
|
|
602
|
+
cursor: grabbing;
|
|
603
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.my-custom-draggable:active {
|
|
607
|
+
cursor: grabbing;
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### 拖拽状态样式
|
|
612
|
+
|
|
613
|
+
```tsx
|
|
614
|
+
function DraggableWithState() {
|
|
615
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
616
|
+
|
|
617
|
+
return (
|
|
618
|
+
<DraggableBox
|
|
619
|
+
className={`custom-draggable ${isDragging ? 'dragging' : ''}`}
|
|
620
|
+
onMouseDown={() => setIsDragging(true)}
|
|
621
|
+
onMouseUp={() => setIsDragging(false)}
|
|
622
|
+
>
|
|
623
|
+
<div>{isDragging ? '拖拽中...' : '可以拖拽'}</div>
|
|
624
|
+
</DraggableBox>
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
```css
|
|
630
|
+
.custom-draggable {
|
|
631
|
+
background: white;
|
|
632
|
+
padding: 16px;
|
|
633
|
+
border-radius: 8px;
|
|
634
|
+
transition: all 0.2s ease;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.custom-draggable.dragging {
|
|
638
|
+
background: #f0f8ff;
|
|
639
|
+
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.2);
|
|
640
|
+
transform: scale(1.02);
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### 响应式样式
|
|
645
|
+
|
|
646
|
+
```tsx
|
|
647
|
+
function ResponsiveDraggable() {
|
|
648
|
+
return (
|
|
649
|
+
<DraggableBox className="responsive-draggable">
|
|
650
|
+
<div>响应式拖拽框</div>
|
|
651
|
+
</DraggableBox>
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
```css
|
|
657
|
+
.responsive-draggable {
|
|
658
|
+
max-width: 300px;
|
|
659
|
+
min-width: 200px;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/* 移动端适配 */
|
|
663
|
+
@media (max-width: 768px) {
|
|
664
|
+
.responsive-draggable {
|
|
665
|
+
max-width: 250px;
|
|
666
|
+
font-size: 14px;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### 主题化样式
|
|
672
|
+
|
|
673
|
+
```tsx
|
|
674
|
+
function ThemedDraggable({ theme = 'light' }) {
|
|
675
|
+
return (
|
|
676
|
+
<DraggableBox className={`draggable-theme-${theme}`}>
|
|
677
|
+
<div>主题化拖拽框 - {theme}</div>
|
|
678
|
+
</DraggableBox>
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
```css
|
|
684
|
+
.draggable-theme-light {
|
|
685
|
+
background: white;
|
|
686
|
+
color: #333;
|
|
687
|
+
border: 1px solid #e0e0e0;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.draggable-theme-dark {
|
|
691
|
+
background: #1a1a1a;
|
|
692
|
+
color: white;
|
|
693
|
+
border: 1px solid #444;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.draggable-theme-accent {
|
|
697
|
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
698
|
+
color: white;
|
|
699
|
+
border: none;
|
|
700
|
+
}
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
## 📊 浏览器兼容性
|
|
704
|
+
|
|
705
|
+
- ✅ Chrome 60+
|
|
706
|
+
- ✅ Firefox 55+
|
|
707
|
+
- ✅ Safari 12+
|
|
708
|
+
- ✅ Edge 79+
|
|
709
|
+
|
|
710
|
+
## 🔗 相关链接
|
|
711
|
+
|
|
712
|
+
- [源码](https://github.com/WuQiuYang/ohkit/tree/main/src/components/draggable-box)
|
|
713
|
+
- [Storybook 演示](https://wuqiuyang.github.io/ohkit/storybook-static)
|
|
714
|
+
- [Issue 反馈](https://github.com/WuQiuYang/ohkit/issues)
|
|
715
|
+
|
|
716
|
+
## 📄 许可证
|
|
717
|
+
|
|
718
|
+
ISC License
|