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 CHANGED
@@ -1,37 +1,204 @@
1
- # use-canvas-drag
2
-
3
- Vue3 画布拖拽组合式函数,支持多种鼠标触发方式。
4
-
5
- ## 安装
6
-
7
- \`\`\`bash
8
- npm install use-canvas-drag
9
- \`\`\`
10
-
11
- ## 使用
12
-
13
- \`\`\`typescript
14
- import { useCanvasDrag } from 'use-canvas-drag'
15
-
16
- const { isPanning, handlers, stopPan } = useCanvasDrag({
17
- container: '#canvas',
18
- mode: 'right', // 可选: 'left' | 'right' | 'middle' | 'shift-left' | 'ctrl-left' | 'alt-left'
19
- })
20
- \`\`\`
21
-
22
- ## API
23
-
24
- ### useCanvasDrag(options)
25
-
26
- | 参数 | 类型 | 说明 |
27
- | ----------- | ----------------------------------------- | ---------------------- |
28
- | container | string\| HTMLElement \| () => HTMLElement | 画布容器 |
29
- | mode | DragTrigger\| DragTrigger[] | 触发方式,默认 'right' |
30
- | enabled | boolean | 是否启用,默认 true |
31
- | onStartDrag | (e: MouseEvent) => void | 开始拖拽回调 |
32
- | onDrag | ({x, y}) => void | 拖拽中回调 |
33
- | onEndDrag | () => void | 结束拖拽回调 |
34
-
35
- ## License
36
-
37
- MIT
1
+ # use-canvas-drag
2
+
3
+ ![npm](https://img.shields.io/npm/v/use-canvas-drag)
4
+ ![npm](https://img.shields.io/npm/dw/use-canvas-drag)
5
+ ![GitHub](https://img.shields.io/github/license/liuruibo/use-canvas-drag)
6
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
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});
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-canvas-drag",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Vue3 画布拖拽组合式函数,支持多种触发方式",
5
5
  "main": "./dist/index.umd.js",
6
6
  "module": "./dist/index.esm.js",