use-canvas-drag 0.1.0 → 0.2.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/README.md +204 -37
- package/dist/index.esm.js +81 -0
- package/dist/index.umd.js +1 -1
- package/dist/src/index.d.ts +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,37 +1,204 @@
|
|
|
1
|
-
# use-canvas-drag
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
# use-canvas-drag
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Vue3 画布拖拽组合式函数,轻量、无依赖、TypeScript 支持,开箱即用。
|
|
9
|
+
|
|
10
|
+
## 特性
|
|
11
|
+
|
|
12
|
+
- 🌿 **轻量无依赖** - 仅 ~1KB,零外部依赖
|
|
13
|
+
- 🔧 **多种触发方式** - 支持右键、中键、左键 + 组合键
|
|
14
|
+
- 📦 **开箱即用** - 组合式函数设计,天然 Tree-Shakable
|
|
15
|
+
- 🔰 **TypeScript** - 完整类型提示,IDE 友好
|
|
16
|
+
- 🎯 **零外部依赖** - 不依赖任何 UI 框架
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install use-canvas-drag
|
|
22
|
+
# 或
|
|
23
|
+
pnpm add use-canvas-drag
|
|
24
|
+
# 或
|
|
25
|
+
yarn add use-canvas-drag
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 快速开始
|
|
29
|
+
|
|
30
|
+
```vue
|
|
31
|
+
<template>
|
|
32
|
+
<div id="canvas" ref="canvasRef" class="canvas">
|
|
33
|
+
<div class="draggable-box" :style="{ left: x + 'px', top: y + 'px' }">
|
|
34
|
+
拖动我
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup>
|
|
40
|
+
import { ref } from 'vue'
|
|
41
|
+
import { useCanvasDrag } from 'use-canvas-drag'
|
|
42
|
+
|
|
43
|
+
const canvasRef = ref(null)
|
|
44
|
+
const x = ref(100)
|
|
45
|
+
const y = ref(100)
|
|
46
|
+
|
|
47
|
+
const { isPanning, stopPan } = useCanvasDrag({
|
|
48
|
+
container: canvasRef,
|
|
49
|
+
mode: 'right',
|
|
50
|
+
onDrag: ({ x: dx, y: dy }) => {
|
|
51
|
+
x.value += dx
|
|
52
|
+
y.value += dy
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<style scoped>
|
|
58
|
+
.canvas {
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 500px;
|
|
61
|
+
border: 1px solid #ddd;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
position: relative;
|
|
64
|
+
}
|
|
65
|
+
.draggable-box {
|
|
66
|
+
position: absolute;
|
|
67
|
+
padding: 20px;
|
|
68
|
+
background: #4a90e2;
|
|
69
|
+
color: white;
|
|
70
|
+
cursor: grab;
|
|
71
|
+
user-select: none;
|
|
72
|
+
}
|
|
73
|
+
.draggable-box:active {
|
|
74
|
+
cursor: grabbing;
|
|
75
|
+
}
|
|
76
|
+
</style>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 多按钮配置
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { useCanvasDrag } from 'use-canvas-drag'
|
|
83
|
+
|
|
84
|
+
const { handlers, stopPan } = useCanvasDrag({
|
|
85
|
+
container: '#canvas',
|
|
86
|
+
mode: ['right', 'middle'], // 右键或中键触发
|
|
87
|
+
enabled: true,
|
|
88
|
+
onStartDrag: (e) => {
|
|
89
|
+
console.log('开始拖拽', e.clientX, e.clientY)
|
|
90
|
+
},
|
|
91
|
+
onDrag: ({ x, y, deltaX, deltaY }) => {
|
|
92
|
+
console.log(`移动了: ${deltaX}, ${deltaY}`)
|
|
93
|
+
},
|
|
94
|
+
onEndDrag: () => {
|
|
95
|
+
console.log('结束拖拽')
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 画布平移
|
|
101
|
+
|
|
102
|
+
```vue
|
|
103
|
+
<template>
|
|
104
|
+
<div id="canvas" ref="canvasRef" class="canvas">
|
|
105
|
+
<div class="content" :style="{ transform: `translate(${offset.x}px, ${offset.y}px)` }">
|
|
106
|
+
<div class="item">Item 1</div>
|
|
107
|
+
<div class="item">Item 2</div>
|
|
108
|
+
<div class="item">Item 3</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</template>
|
|
112
|
+
|
|
113
|
+
<script setup>
|
|
114
|
+
import { reactive } from 'vue'
|
|
115
|
+
import { useCanvasDrag } from 'use-canvas-drag'
|
|
116
|
+
|
|
117
|
+
const canvasRef = ref(null)
|
|
118
|
+
const offset = reactive({ x: 0, y: 0 })
|
|
119
|
+
|
|
120
|
+
const { isPanning } = useCanvasDrag({
|
|
121
|
+
container: canvasRef,
|
|
122
|
+
mode: 'right',
|
|
123
|
+
onDrag: ({ deltaX, deltaY }) => {
|
|
124
|
+
offset.x += deltaX
|
|
125
|
+
offset.y += deltaY
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
</script>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## API
|
|
132
|
+
|
|
133
|
+
### useCanvasDrag(options)
|
|
134
|
+
|
|
135
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
136
|
+
|------|------|------|------|
|
|
137
|
+
| `container` | `string \| HTMLElement \| () => HTMLElement` | ✅ | 画布容器,支持选择器、元素或返回元素的函数 |
|
|
138
|
+
| `mode` | `'left' \| 'right' \| 'middle' \| 'shift-left' \| 'ctrl-left' \| 'alt-left' \| (Trigger)[]'` | ❌ | 触发方式,默认 `'right'` |
|
|
139
|
+
| `enabled` | `boolean` | ❌ | 是否启用,默认 `true` |
|
|
140
|
+
| `onStartDrag` | `(e: MouseEvent) => void` | ❌ | 开始拖拽时触发 |
|
|
141
|
+
| `onDrag` | `(info: DragInfo) => void` | ❌ | 拖拽过程中触发 |
|
|
142
|
+
| `onEndDrag` | `() => void` | ❌ | 结束拖拽时触发 |
|
|
143
|
+
|
|
144
|
+
### DragInfo
|
|
145
|
+
|
|
146
|
+
| 属性 | 类型 | 说明 |
|
|
147
|
+
|------|------|------|
|
|
148
|
+
| `x` | `number` | 相对于起始位置的 X 轴偏移 |
|
|
149
|
+
| `y` | `number` | 相对于起始位置的 Y 轴偏移 |
|
|
150
|
+
| `deltaX` | `number` | 相对于上一帧的 X 轴偏移 |
|
|
151
|
+
| `deltaY` | `number` | 相对于上一帧的 Y 轴偏移 |
|
|
152
|
+
|
|
153
|
+
### 返回值
|
|
154
|
+
|
|
155
|
+
| 属性 | 类型 | 说明 |
|
|
156
|
+
|------|------|------|
|
|
157
|
+
| `isPanning` | `Ref<boolean>` | 是否正在平移 |
|
|
158
|
+
| `handlers` | `object` | mousedown/mousemove/mouseup 事件处理器 |
|
|
159
|
+
| `stopPan` | `() => void` | 手动停止平移 |
|
|
160
|
+
| `setEnabled` | `(enabled: boolean) => void` | 动态启用/禁用 |
|
|
161
|
+
|
|
162
|
+
### DragButtonConfig (类型导出)
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
interface DragButtonConfig {
|
|
166
|
+
button: number; // 鼠标按钮: 0=左键, 1=中键, 2=右键
|
|
167
|
+
ctrlKey?: boolean; // 是否需要 Ctrl 键
|
|
168
|
+
shiftKey?: boolean; // 是否需要 Shift 键
|
|
169
|
+
altKey?: boolean; // 是否需要 Alt 键
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 触发模式
|
|
174
|
+
|
|
175
|
+
| 模式 | 说明 |
|
|
176
|
+
|------|------|
|
|
177
|
+
| `'left'` | 鼠标左键 |
|
|
178
|
+
| `'right'` | 鼠标右键 |
|
|
179
|
+
| `'middle'` | 鼠标中键(滚轮) |
|
|
180
|
+
| `'shift-left'` | Shift + 左键 |
|
|
181
|
+
| `'ctrl-left'` | Ctrl + 左键 |
|
|
182
|
+
| `'alt-left'` | Alt + 左键 |
|
|
183
|
+
|
|
184
|
+
## 常见问题
|
|
185
|
+
|
|
186
|
+
### Q: 右键菜单被阻止了吗?
|
|
187
|
+
|
|
188
|
+
A: 是的,插件会自动调用 `e.preventDefault()` 阻止默认右键菜单。
|
|
189
|
+
|
|
190
|
+
### Q: 如何在拖拽时隐藏自定义鼠标指针?
|
|
191
|
+
|
|
192
|
+
A: 通过 `isPanning` 控制:
|
|
193
|
+
|
|
194
|
+
```vue
|
|
195
|
+
<div :class="{ 'hide-cursor': isPanning }">...</div>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Q: 支持多画布吗?
|
|
199
|
+
|
|
200
|
+
A: 支持,只需创建多个实例即可。
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT
|
package/dist/index.esm.js
CHANGED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { computed as e, onUnmounted as t, ref as n, watch as r } from "vue";
|
|
2
|
+
//#region src/useCanvasDrag.ts
|
|
3
|
+
var i = ["right"], a = {
|
|
4
|
+
left: { button: 0 },
|
|
5
|
+
right: { button: 2 },
|
|
6
|
+
middle: { button: 1 },
|
|
7
|
+
"shift-left": {
|
|
8
|
+
button: 0,
|
|
9
|
+
modifiers: { shift: !0 }
|
|
10
|
+
},
|
|
11
|
+
"ctrl-left": {
|
|
12
|
+
button: 0,
|
|
13
|
+
modifiers: { ctrl: !0 }
|
|
14
|
+
},
|
|
15
|
+
"alt-left": {
|
|
16
|
+
button: 0,
|
|
17
|
+
modifiers: { alt: !0 }
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function o(e) {
|
|
21
|
+
return e ? e.map((e) => typeof e == "string" ? a[e] ?? { button: -1 } : e) : i.map((e) => a[e]);
|
|
22
|
+
}
|
|
23
|
+
function s(i) {
|
|
24
|
+
let { container: a, enabled: s = !0, onStartDrag: c, onDrag: l, onEndDrag: u, mode: d } = i, f = n(!1), p = n({
|
|
25
|
+
x: 0,
|
|
26
|
+
y: 0,
|
|
27
|
+
scrollLeft: 0,
|
|
28
|
+
scrollTop: 0
|
|
29
|
+
}), m = e(() => o(Array.isArray(d) ? d : d ? [d] : void 0)), h = () => typeof a == "string" ? document.querySelector(a) : typeof a == "function" ? a() : a instanceof HTMLElement ? a : null, g = (e) => m.value.some((t) => {
|
|
30
|
+
if (t.button !== void 0 && t.button !== e.button) return !1;
|
|
31
|
+
if (t.modifiers) {
|
|
32
|
+
let n = t.modifiers;
|
|
33
|
+
if (n.shift && !e.shiftKey || n.ctrl && !e.ctrlKey || n.alt && !e.altKey || n.meta && !e.metaKey) return !1;
|
|
34
|
+
}
|
|
35
|
+
return !0;
|
|
36
|
+
}), _ = (e) => {
|
|
37
|
+
if (!s || !g(e)) return;
|
|
38
|
+
let t = h();
|
|
39
|
+
t && (p.value = {
|
|
40
|
+
x: e.clientX,
|
|
41
|
+
y: e.clientY,
|
|
42
|
+
scrollLeft: t.scrollLeft,
|
|
43
|
+
scrollTop: t.scrollTop
|
|
44
|
+
}, f.value = !0, t.style.cursor = "grabbing", t.style.userSelect = "none", e.preventDefault(), c?.(e), document.addEventListener("mousemove", v), document.addEventListener("mouseup", y));
|
|
45
|
+
}, v = (e) => {
|
|
46
|
+
if (!f.value) return;
|
|
47
|
+
let t = h();
|
|
48
|
+
if (!t) return;
|
|
49
|
+
let n = e.clientX - p.value.x, r = e.clientY - p.value.y;
|
|
50
|
+
t.scrollLeft = p.value.scrollLeft - n, t.scrollTop = p.value.scrollTop - r, l?.({
|
|
51
|
+
x: n,
|
|
52
|
+
y: r
|
|
53
|
+
});
|
|
54
|
+
}, y = (e) => {
|
|
55
|
+
if (!f.value) return;
|
|
56
|
+
f.value = !1;
|
|
57
|
+
let t = h();
|
|
58
|
+
t && (t.style.cursor = "auto", t.style.userSelect = ""), document.removeEventListener("mousemove", v), document.removeEventListener("mouseup", y), u?.();
|
|
59
|
+
}, b = (e) => {
|
|
60
|
+
!s || !g(e) || e.preventDefault();
|
|
61
|
+
}, x = () => {
|
|
62
|
+
if (!f.value) return;
|
|
63
|
+
f.value = !1;
|
|
64
|
+
let e = h();
|
|
65
|
+
e && (e.style.cursor = "auto", e.style.userSelect = ""), document.removeEventListener("mousemove", v), document.removeEventListener("mouseup", y), u?.();
|
|
66
|
+
};
|
|
67
|
+
return r(() => s, (e) => {
|
|
68
|
+
!e && f.value && x();
|
|
69
|
+
}), t(() => {
|
|
70
|
+
document.removeEventListener("mousemove", v), document.removeEventListener("mouseup", y);
|
|
71
|
+
}), {
|
|
72
|
+
isPanning: f,
|
|
73
|
+
handlers: {
|
|
74
|
+
onMouseDown: _,
|
|
75
|
+
onContextMenu: b
|
|
76
|
+
},
|
|
77
|
+
stopPan: x
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { s as useCanvasDrag };
|
package/dist/index.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(e){typeof define==`function`&&define.amd?define([],e):e()})(function(){});
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`vue`)):typeof define==`function`&&define.amd?define([`exports`,`vue`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.UseCanvasDrag={},e.Vue))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=[`right`],r={left:{button:0},right:{button:2},middle:{button:1},"shift-left":{button:0,modifiers:{shift:!0}},"ctrl-left":{button:0,modifiers:{ctrl:!0}},"alt-left":{button:0,modifiers:{alt:!0}}};function i(e){return e?e.map(e=>typeof e==`string`?r[e]??{button:-1}:e):n.map(e=>r[e])}function a(e){let{container:n,enabled:r=!0,onStartDrag:a,onDrag:o,onEndDrag:s,mode:c}=e,l=(0,t.ref)(!1),u=(0,t.ref)({x:0,y:0,scrollLeft:0,scrollTop:0}),d=(0,t.computed)(()=>i(Array.isArray(c)?c:c?[c]:void 0)),f=()=>typeof n==`string`?document.querySelector(n):typeof n==`function`?n():n instanceof HTMLElement?n:null,p=e=>d.value.some(t=>{if(t.button!==void 0&&t.button!==e.button)return!1;if(t.modifiers){let n=t.modifiers;if(n.shift&&!e.shiftKey||n.ctrl&&!e.ctrlKey||n.alt&&!e.altKey||n.meta&&!e.metaKey)return!1}return!0}),m=e=>{if(!r||!p(e))return;let t=f();t&&(u.value={x:e.clientX,y:e.clientY,scrollLeft:t.scrollLeft,scrollTop:t.scrollTop},l.value=!0,t.style.cursor=`grabbing`,t.style.userSelect=`none`,e.preventDefault(),a?.(e),document.addEventListener(`mousemove`,h),document.addEventListener(`mouseup`,g))},h=e=>{if(!l.value)return;let t=f();if(!t)return;let n=e.clientX-u.value.x,r=e.clientY-u.value.y;t.scrollLeft=u.value.scrollLeft-n,t.scrollTop=u.value.scrollTop-r,o?.({x:n,y:r})},g=e=>{if(!l.value)return;l.value=!1;let t=f();t&&(t.style.cursor=`auto`,t.style.userSelect=``),document.removeEventListener(`mousemove`,h),document.removeEventListener(`mouseup`,g),s?.()},_=e=>{!r||!p(e)||e.preventDefault()},v=()=>{if(!l.value)return;l.value=!1;let e=f();e&&(e.style.cursor=`auto`,e.style.userSelect=``),document.removeEventListener(`mousemove`,h),document.removeEventListener(`mouseup`,g),s?.()};return(0,t.watch)(()=>r,e=>{!e&&l.value&&v()}),(0,t.onUnmounted)(()=>{document.removeEventListener(`mousemove`,h),document.removeEventListener(`mouseup`,g)}),{isPanning:l,handlers:{onMouseDown:m,onContextMenu:_},stopPan:v}}e.useCanvasDrag=a});
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 主入口文件
|
|
3
|
+
* @author lrb
|
|
4
|
+
* @date 2026-05-11 23:21:24
|
|
5
|
+
* @lastModifyTime 2026-05-11 23:21:24
|
|
6
|
+
* @lastModifyAuthor lrb
|
|
7
|
+
* @license MIT
|
|
8
|
+
* @copyright lrb
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
export { useCanvasDrag } from './useCanvasDrag';
|
|
1
12
|
export type { DragButtonConfig, CanvasDragOptions, DragTrigger } from './useCanvasDrag';
|