canvas-drawing-editor 2.0.2 → 2.0.4
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
|
@@ -128,16 +128,102 @@ function App() {
|
|
|
128
128
|
| `show-import` | boolean | true | Show JSON load |
|
|
129
129
|
| `show-color` | boolean | true | Show color picker |
|
|
130
130
|
| `show-minimap` | boolean | true | Show navigation minimap |
|
|
131
|
+
| `initial-data` | string | - | Initial JSON data to render (see format below) |
|
|
132
|
+
|
|
133
|
+
### 📊 Initial Data
|
|
134
|
+
|
|
135
|
+
You can pass JSON data to initialize the canvas content:
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<canvas-drawing-editor
|
|
139
|
+
initial-data='{"objects":[{"id":"abc123","type":"RECTANGLE","x":100,"y":100,"width":200,"height":150,"color":"#3b82f6","lineWidth":2}]}'
|
|
140
|
+
></canvas-drawing-editor>
|
|
141
|
+
```
|
|
131
142
|
|
|
132
143
|
### 📡 Events
|
|
133
144
|
|
|
145
|
+
#### `editor-change` Event
|
|
146
|
+
|
|
147
|
+
Fires when canvas content changes. The event detail contains:
|
|
148
|
+
|
|
149
|
+
| Property | Type | Description |
|
|
150
|
+
|----------|------|-------------|
|
|
151
|
+
| `objects` | Array | All drawing objects (raw data) |
|
|
152
|
+
| `data` | Object | Serializable JSON data for storage/restore |
|
|
153
|
+
|
|
134
154
|
```javascript
|
|
135
|
-
// Listen for canvas changes
|
|
136
155
|
document.addEventListener('editor-change', (e) => {
|
|
137
156
|
console.log('Objects:', e.detail.objects);
|
|
157
|
+
|
|
158
|
+
// Use e.detail.data for saving to database
|
|
159
|
+
const jsonData = JSON.stringify(e.detail.data);
|
|
160
|
+
localStorage.setItem('canvas-data', jsonData);
|
|
161
|
+
// or save to server: fetch('/api/save', { method: 'POST', body: jsonData });
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Object Types & Properties
|
|
166
|
+
|
|
167
|
+
Each object in `e.detail.objects` has the following base properties:
|
|
168
|
+
|
|
169
|
+
| Property | Type | Description |
|
|
170
|
+
|----------|------|-------------|
|
|
171
|
+
| `id` | string | Unique identifier |
|
|
172
|
+
| `type` | string | Object type: `RECTANGLE`, `CIRCLE`, `PATH`, `TEXT`, `IMAGE` |
|
|
173
|
+
| `x` | number | X coordinate |
|
|
174
|
+
| `y` | number | Y coordinate |
|
|
175
|
+
| `color` | string | Stroke/fill color (hex format, e.g., `#3b82f6`) |
|
|
176
|
+
| `lineWidth` | number | Line width in pixels |
|
|
177
|
+
|
|
178
|
+
**Rectangle** (`type: "RECTANGLE"`):
|
|
179
|
+
| Property | Type | Description |
|
|
180
|
+
|----------|------|-------------|
|
|
181
|
+
| `width` | number | Rectangle width |
|
|
182
|
+
| `height` | number | Rectangle height |
|
|
183
|
+
|
|
184
|
+
**Circle** (`type: "CIRCLE"`):
|
|
185
|
+
| Property | Type | Description |
|
|
186
|
+
|----------|------|-------------|
|
|
187
|
+
| `radius` | number | Circle radius |
|
|
188
|
+
|
|
189
|
+
**Path/Pencil** (`type: "PATH"`):
|
|
190
|
+
| Property | Type | Description |
|
|
191
|
+
|----------|------|-------------|
|
|
192
|
+
| `points` | Array<{x, y}> | Array of point coordinates |
|
|
193
|
+
|
|
194
|
+
**Text** (`type: "TEXT"`):
|
|
195
|
+
| Property | Type | Description |
|
|
196
|
+
|----------|------|-------------|
|
|
197
|
+
| `text` | string | Text content |
|
|
198
|
+
| `fontSize` | number | Font size in pixels |
|
|
199
|
+
|
|
200
|
+
**Image** (`type: "IMAGE"`):
|
|
201
|
+
| Property | Type | Description |
|
|
202
|
+
|----------|------|-------------|
|
|
203
|
+
| `width` | number | Image width |
|
|
204
|
+
| `height` | number | Image height |
|
|
205
|
+
| `dataUrl` | string | Base64 encoded image data |
|
|
206
|
+
|
|
207
|
+
#### Example: Saving and Loading
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
// Save canvas content (use e.detail.data for clean serializable data)
|
|
211
|
+
document.addEventListener('editor-change', (e) => {
|
|
212
|
+
const jsonData = JSON.stringify(e.detail.data);
|
|
213
|
+
localStorage.setItem('my-canvas', jsonData);
|
|
214
|
+
// or save to database
|
|
138
215
|
});
|
|
139
216
|
|
|
140
|
-
//
|
|
217
|
+
// Load canvas content
|
|
218
|
+
const savedData = localStorage.getItem('my-canvas');
|
|
219
|
+
if (savedData) {
|
|
220
|
+
document.querySelector('canvas-drawing-editor').setAttribute('initial-data', savedData);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### `editor-close` Event
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
141
227
|
document.addEventListener('editor-close', () => {
|
|
142
228
|
console.log('Editor closed');
|
|
143
229
|
});
|
|
@@ -282,16 +368,102 @@ function App() {
|
|
|
282
368
|
| `show-import` | boolean | true | 显示 JSON 加载 |
|
|
283
369
|
| `show-color` | boolean | true | 显示颜色选择器 |
|
|
284
370
|
| `show-minimap` | boolean | true | 显示导航小地图 |
|
|
371
|
+
| `initial-data` | string | - | 初始化 JSON 数据(格式见下方) |
|
|
372
|
+
|
|
373
|
+
### 📊 初始化数据
|
|
374
|
+
|
|
375
|
+
可以通过 `initial-data` 属性传入 JSON 数据来初始化画布内容:
|
|
376
|
+
|
|
377
|
+
```html
|
|
378
|
+
<canvas-drawing-editor
|
|
379
|
+
initial-data='{"objects":[{"id":"abc123","type":"RECTANGLE","x":100,"y":100,"width":200,"height":150,"color":"#3b82f6","lineWidth":2}]}'
|
|
380
|
+
></canvas-drawing-editor>
|
|
381
|
+
```
|
|
285
382
|
|
|
286
383
|
### 📡 事件监听
|
|
287
384
|
|
|
385
|
+
#### `editor-change` 事件
|
|
386
|
+
|
|
387
|
+
当画布内容变化时触发。事件详情包含:
|
|
388
|
+
|
|
389
|
+
| 属性 | 类型 | 说明 |
|
|
390
|
+
|------|------|------|
|
|
391
|
+
| `objects` | Array | 所有绑图对象(原始数据) |
|
|
392
|
+
| `data` | Object | 可序列化的 JSON 数据,用于存储/回显 |
|
|
393
|
+
|
|
288
394
|
```javascript
|
|
289
|
-
// 监听画布内容变化
|
|
290
395
|
document.addEventListener('editor-change', (e) => {
|
|
291
396
|
console.log('对象列表:', e.detail.objects);
|
|
397
|
+
|
|
398
|
+
// 使用 e.detail.data 保存到数据库
|
|
399
|
+
const jsonData = JSON.stringify(e.detail.data);
|
|
400
|
+
localStorage.setItem('canvas-data', jsonData);
|
|
401
|
+
// 或保存到服务器: fetch('/api/save', { method: 'POST', body: jsonData });
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
#### 对象类型和属性说明
|
|
406
|
+
|
|
407
|
+
`e.detail.objects` 中每个对象都有以下基础属性:
|
|
408
|
+
|
|
409
|
+
| 属性 | 类型 | 说明 |
|
|
410
|
+
|------|------|------|
|
|
411
|
+
| `id` | string | 唯一标识符 |
|
|
412
|
+
| `type` | string | 对象类型:`RECTANGLE`、`CIRCLE`、`PATH`、`TEXT`、`IMAGE` |
|
|
413
|
+
| `x` | number | X 坐标 |
|
|
414
|
+
| `y` | number | Y 坐标 |
|
|
415
|
+
| `color` | string | 描边/填充颜色(十六进制格式,如 `#3b82f6`) |
|
|
416
|
+
| `lineWidth` | number | 线条宽度(像素) |
|
|
417
|
+
|
|
418
|
+
**矩形** (`type: "RECTANGLE"`):
|
|
419
|
+
| 属性 | 类型 | 说明 |
|
|
420
|
+
|------|------|------|
|
|
421
|
+
| `width` | number | 矩形宽度 |
|
|
422
|
+
| `height` | number | 矩形高度 |
|
|
423
|
+
|
|
424
|
+
**圆形** (`type: "CIRCLE"`):
|
|
425
|
+
| 属性 | 类型 | 说明 |
|
|
426
|
+
|------|------|------|
|
|
427
|
+
| `radius` | number | 圆形半径 |
|
|
428
|
+
|
|
429
|
+
**画笔路径** (`type: "PATH"`):
|
|
430
|
+
| 属性 | 类型 | 说明 |
|
|
431
|
+
|------|------|------|
|
|
432
|
+
| `points` | Array<{x, y}> | 点坐标数组 |
|
|
433
|
+
|
|
434
|
+
**文本** (`type: "TEXT"`):
|
|
435
|
+
| 属性 | 类型 | 说明 |
|
|
436
|
+
|------|------|------|
|
|
437
|
+
| `text` | string | 文本内容 |
|
|
438
|
+
| `fontSize` | number | 字体大小(像素) |
|
|
439
|
+
|
|
440
|
+
**图片** (`type: "IMAGE"`):
|
|
441
|
+
| 属性 | 类型 | 说明 |
|
|
442
|
+
|------|------|------|
|
|
443
|
+
| `width` | number | 图片宽度 |
|
|
444
|
+
| `height` | number | 图片高度 |
|
|
445
|
+
| `dataUrl` | string | Base64 编码的图片数据 |
|
|
446
|
+
|
|
447
|
+
#### 示例:保存和加载画布
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
// 保存画布内容(使用 e.detail.data 获取可序列化的数据)
|
|
451
|
+
document.addEventListener('editor-change', (e) => {
|
|
452
|
+
const jsonData = JSON.stringify(e.detail.data);
|
|
453
|
+
localStorage.setItem('my-canvas', jsonData);
|
|
454
|
+
// 或保存到数据库
|
|
292
455
|
});
|
|
293
456
|
|
|
294
|
-
//
|
|
457
|
+
// 加载画布内容
|
|
458
|
+
const savedData = localStorage.getItem('my-canvas');
|
|
459
|
+
if (savedData) {
|
|
460
|
+
document.querySelector('canvas-drawing-editor').setAttribute('initial-data', savedData);
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### `editor-close` 事件
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
295
467
|
document.addEventListener('editor-close', () => {
|
|
296
468
|
console.log('编辑器已关闭');
|
|
297
469
|
});
|
|
@@ -12,7 +12,7 @@ const f = {
|
|
|
12
12
|
showColor: !0,
|
|
13
13
|
showMinimap: !0
|
|
14
14
|
};
|
|
15
|
-
class
|
|
15
|
+
class b extends HTMLElement {
|
|
16
16
|
constructor() {
|
|
17
17
|
super(), this.config = { ...f }, this.objects = [], this.selectedId = null, this.tool = "SELECT", this.color = "#000000", this.lineWidth = 3, this.isDragging = !1, this.dragStart = null, this.currentObject = null, this.dragOffset = { x: 0, y: 0 }, this.isTextInputVisible = !1, this.textInputPos = { x: 0, y: 0 }, this.textInputScreenPos = { x: 0, y: 0 }, this.editingTextId = null, this.isResizing = !1, this.resizeHandle = null, this.resizeStartBounds = null, this.resizeOriginalObject = null, this.history = [], this.clipboard = null, this.scale = 1, this.panOffset = { x: 0, y: 0 }, this.isPanning = !1, this.panStart = { x: 0, y: 0 }, this.shadow = this.attachShadow({ mode: "open" }), this.boundHandleResize = this.handleResize.bind(this), this.boundHandleKeyDown = this.handleKeyDown.bind(this), this.boundHandleWheel = this.handleWheel.bind(this);
|
|
18
18
|
}
|
|
@@ -30,12 +30,13 @@ class v extends HTMLElement {
|
|
|
30
30
|
"show-export",
|
|
31
31
|
"show-import",
|
|
32
32
|
"show-color",
|
|
33
|
-
"show-minimap"
|
|
33
|
+
"show-minimap",
|
|
34
|
+
"initial-data"
|
|
34
35
|
];
|
|
35
36
|
}
|
|
36
37
|
// 生命周期:连接到 DOM
|
|
37
38
|
connectedCallback() {
|
|
38
|
-
this.parseAttributes(), this.render(), this.setupEventListeners(), this.initCanvas();
|
|
39
|
+
this.parseAttributes(), this.render(), this.setupEventListeners(), this.initCanvas(), this.loadInitialData();
|
|
39
40
|
}
|
|
40
41
|
// 生命周期:从 DOM 断开
|
|
41
42
|
disconnectedCallback() {
|
|
@@ -43,7 +44,13 @@ class v extends HTMLElement {
|
|
|
43
44
|
}
|
|
44
45
|
// 生命周期:属性变化
|
|
45
46
|
attributeChangedCallback(t, i, s) {
|
|
46
|
-
i !== s
|
|
47
|
+
if (i !== s) {
|
|
48
|
+
if (t === "initial-data" && s && this.canvas) {
|
|
49
|
+
this.loadInitialData();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.parseAttributes(), this.container && this.updateUI();
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
// 解析 HTML 属性
|
|
49
56
|
parseAttributes() {
|
|
@@ -66,6 +73,24 @@ class v extends HTMLElement {
|
|
|
66
73
|
generateId() {
|
|
67
74
|
return Math.random().toString(36).substr(2, 9);
|
|
68
75
|
}
|
|
76
|
+
// 加载初始数据
|
|
77
|
+
loadInitialData() {
|
|
78
|
+
const t = this.getAttribute("initial-data");
|
|
79
|
+
if (t)
|
|
80
|
+
try {
|
|
81
|
+
const i = JSON.parse(t);
|
|
82
|
+
i.objects && Array.isArray(i.objects) && (this.objects = i.objects, this.selectedId = null, this.objects.forEach((s) => {
|
|
83
|
+
if (s.type === "IMAGE" && s.dataUrl) {
|
|
84
|
+
const e = new Image();
|
|
85
|
+
e.onload = () => {
|
|
86
|
+
s.imageElement = e, this.renderCanvas(), this.renderMinimap();
|
|
87
|
+
}, e.src = s.dataUrl;
|
|
88
|
+
}
|
|
89
|
+
}), this.renderCanvas(), this.renderMinimap(), this.updateUI());
|
|
90
|
+
} catch (i) {
|
|
91
|
+
console.error("Failed to parse initial-data:", i);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
69
94
|
// 设置事件监听
|
|
70
95
|
setupEventListeners() {
|
|
71
96
|
window.addEventListener("resize", this.boundHandleResize), window.addEventListener("keydown", this.boundHandleKeyDown);
|
|
@@ -94,8 +119,8 @@ class v extends HTMLElement {
|
|
|
94
119
|
s = t.clientX, e = t.clientY;
|
|
95
120
|
else
|
|
96
121
|
return { x: 0, y: 0 };
|
|
97
|
-
const n = (s - i.left - this.panOffset.x) / this.scale,
|
|
98
|
-
return { x: n, y:
|
|
122
|
+
const n = (s - i.left - this.panOffset.x) / this.scale, o = (e - i.top - this.panOffset.y) / this.scale;
|
|
123
|
+
return { x: n, y: o };
|
|
99
124
|
}
|
|
100
125
|
// 获取屏幕坐标(不考虑缩放和平移)
|
|
101
126
|
getScreenPos(t) {
|
|
@@ -128,23 +153,23 @@ class v extends HTMLElement {
|
|
|
128
153
|
case "PATH": {
|
|
129
154
|
const i = t;
|
|
130
155
|
if (i.points.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
|
|
131
|
-
const s = Math.min(...i.points.map((
|
|
132
|
-
return { x: s, y: n, width: e - s, height:
|
|
156
|
+
const s = Math.min(...i.points.map((a) => a.x)), e = Math.max(...i.points.map((a) => a.x)), n = Math.min(...i.points.map((a) => a.y)), o = Math.max(...i.points.map((a) => a.y));
|
|
157
|
+
return { x: s, y: n, width: e - s, height: o - n };
|
|
133
158
|
}
|
|
134
159
|
}
|
|
135
160
|
return { x: 0, y: 0, width: 0, height: 0 };
|
|
136
161
|
}
|
|
137
162
|
// 检查调整大小手柄
|
|
138
163
|
getResizeHandleAtPoint(t, i, s) {
|
|
139
|
-
const e = this.getObjectBounds(t), n = 8,
|
|
164
|
+
const e = this.getObjectBounds(t), n = 8, o = [
|
|
140
165
|
{ name: "nw", x: e.x, y: e.y },
|
|
141
166
|
{ name: "ne", x: e.x + e.width, y: e.y },
|
|
142
167
|
{ name: "sw", x: e.x, y: e.y + e.height },
|
|
143
168
|
{ name: "se", x: e.x + e.width, y: e.y + e.height }
|
|
144
169
|
];
|
|
145
|
-
for (const
|
|
146
|
-
if (Math.abs(i -
|
|
147
|
-
return
|
|
170
|
+
for (const a of o)
|
|
171
|
+
if (Math.abs(i - a.x) <= n && Math.abs(s - a.y) <= n)
|
|
172
|
+
return a.name;
|
|
148
173
|
return null;
|
|
149
174
|
}
|
|
150
175
|
// 碰撞检测
|
|
@@ -169,8 +194,8 @@ class v extends HTMLElement {
|
|
|
169
194
|
case "PATH": {
|
|
170
195
|
const e = t;
|
|
171
196
|
if (e.points.length === 0) return !1;
|
|
172
|
-
const n = Math.min(...e.points.map((r) => r.x)),
|
|
173
|
-
return i >= n && i <=
|
|
197
|
+
const n = Math.min(...e.points.map((r) => r.x)), o = Math.max(...e.points.map((r) => r.x)), a = Math.min(...e.points.map((r) => r.y)), l = Math.max(...e.points.map((r) => r.y));
|
|
198
|
+
return i >= n && i <= o && s >= a && s <= l;
|
|
174
199
|
}
|
|
175
200
|
}
|
|
176
201
|
return !1;
|
|
@@ -214,10 +239,23 @@ class v extends HTMLElement {
|
|
|
214
239
|
}
|
|
215
240
|
// 派发变化事件
|
|
216
241
|
dispatchChangeEvent() {
|
|
242
|
+
const t = {
|
|
243
|
+
objects: this.objects.map((i) => {
|
|
244
|
+
if (i.type === "IMAGE") {
|
|
245
|
+
const { imageElement: s, ...e } = i;
|
|
246
|
+
return e;
|
|
247
|
+
}
|
|
248
|
+
return { ...i };
|
|
249
|
+
})
|
|
250
|
+
};
|
|
217
251
|
this.dispatchEvent(new CustomEvent("editor-change", {
|
|
218
252
|
bubbles: !0,
|
|
219
253
|
composed: !0,
|
|
220
|
-
detail: {
|
|
254
|
+
detail: {
|
|
255
|
+
objects: this.objects,
|
|
256
|
+
data: t
|
|
257
|
+
// 可直接用于存储/回显的 JSON 数据
|
|
258
|
+
}
|
|
221
259
|
}));
|
|
222
260
|
}
|
|
223
261
|
// 键盘事件处理
|
|
@@ -266,13 +304,13 @@ class v extends HTMLElement {
|
|
|
266
304
|
// 滚轮缩放
|
|
267
305
|
handleWheel(t) {
|
|
268
306
|
t.preventDefault();
|
|
269
|
-
const i = this.canvas.getBoundingClientRect(), s = t.clientX - i.left, e = t.clientY - i.top, n = t.deltaY > 0 ? 0.9 : 1.1,
|
|
270
|
-
this.zoomAtPoint(
|
|
307
|
+
const i = this.canvas.getBoundingClientRect(), s = t.clientX - i.left, e = t.clientY - i.top, n = t.deltaY > 0 ? 0.9 : 1.1, o = this.scale * n;
|
|
308
|
+
this.zoomAtPoint(o, s, e);
|
|
271
309
|
}
|
|
272
310
|
// 以指定点为中心缩放
|
|
273
311
|
zoomAtPoint(t, i, s) {
|
|
274
|
-
const e = Math.min(Math.max(t, 0.2), 5), n = (i - this.panOffset.x) / this.scale,
|
|
275
|
-
this.scale = e, this.panOffset = { x:
|
|
312
|
+
const e = Math.min(Math.max(t, 0.2), 5), n = (i - this.panOffset.x) / this.scale, o = (s - this.panOffset.y) / this.scale, a = i - n * e, l = s - o * e;
|
|
313
|
+
this.scale = e, this.panOffset = { x: a, y: l }, this.renderCanvas(), this.renderMinimap(), this.updateZoomDisplay();
|
|
276
314
|
}
|
|
277
315
|
// 放大
|
|
278
316
|
zoomIn() {
|
|
@@ -344,16 +382,16 @@ class v extends HTMLElement {
|
|
|
344
382
|
const { x: i, y: s } = this.getMousePos(t), e = this.getScreenPos(t);
|
|
345
383
|
if (this.dragStart = { x: i, y: s }, this.isDragging = !0, this.isTextInputVisible && this.tool !== "TEXT" && this.submitText(), this.tool === "SELECT") {
|
|
346
384
|
if (this.selectedId) {
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
349
|
-
const
|
|
350
|
-
if (
|
|
351
|
-
this.saveHistory(), this.isResizing = !0, this.resizeHandle =
|
|
385
|
+
const o = this.objects.find((a) => a.id === this.selectedId);
|
|
386
|
+
if (o) {
|
|
387
|
+
const a = this.getResizeHandleAtPoint(o, i, s);
|
|
388
|
+
if (a) {
|
|
389
|
+
this.saveHistory(), this.isResizing = !0, this.resizeHandle = a, this.resizeStartBounds = this.getObjectBounds(o), this.resizeOriginalObject = JSON.parse(JSON.stringify(o));
|
|
352
390
|
return;
|
|
353
391
|
}
|
|
354
392
|
}
|
|
355
393
|
}
|
|
356
|
-
const n = [...this.objects].reverse().find((
|
|
394
|
+
const n = [...this.objects].reverse().find((o) => this.isHit(o, i, s));
|
|
357
395
|
n ? (this.selectedId = n.id, this.dragOffset = { x: i - n.x, y: s - n.y }, this.saveHistory(), this.updateUI()) : (this.selectedId = null, this.isPanning = !0, this.panStart = e, this.updateUI());
|
|
358
396
|
} else if (this.tool === "TEXT")
|
|
359
397
|
this.textInputPos = { x: i, y: s }, this.showTextInput(e.x, e.y), this.isDragging = !1;
|
|
@@ -367,8 +405,8 @@ class v extends HTMLElement {
|
|
|
367
405
|
// 画布鼠标移动
|
|
368
406
|
handleCanvasPointerMove(t) {
|
|
369
407
|
if (this.isPanning) {
|
|
370
|
-
const e = this.getScreenPos(t), n = e.x - this.panStart.x,
|
|
371
|
-
this.panOffset = { x: this.panOffset.x + n, y: this.panOffset.y +
|
|
408
|
+
const e = this.getScreenPos(t), n = e.x - this.panStart.x, o = e.y - this.panStart.y;
|
|
409
|
+
this.panOffset = { x: this.panOffset.x + n, y: this.panOffset.y + o }, this.panStart = e, this.renderCanvas(), this.renderMinimap();
|
|
372
410
|
return;
|
|
373
411
|
}
|
|
374
412
|
if (!this.isDragging || !this.dragStart) return;
|
|
@@ -376,27 +414,27 @@ class v extends HTMLElement {
|
|
|
376
414
|
if (this.isResizing && this.selectedId && this.resizeHandle && this.resizeStartBounds && this.resizeOriginalObject) {
|
|
377
415
|
const e = this.objects.find((d) => d.id === this.selectedId);
|
|
378
416
|
if (!e) return;
|
|
379
|
-
const n = i - this.dragStart.x,
|
|
380
|
-
let
|
|
381
|
-
switch (this.resizeHandle.includes("e") && (r = this.resizeStartBounds.width + n), this.resizeHandle.includes("w") && (
|
|
417
|
+
const n = i - this.dragStart.x, o = s - this.dragStart.y;
|
|
418
|
+
let a = this.resizeStartBounds.x, l = this.resizeStartBounds.y, r = this.resizeStartBounds.width, h = this.resizeStartBounds.height;
|
|
419
|
+
switch (this.resizeHandle.includes("e") && (r = this.resizeStartBounds.width + n), this.resizeHandle.includes("w") && (a = this.resizeStartBounds.x + n, r = this.resizeStartBounds.width - n), this.resizeHandle.includes("s") && (h = this.resizeStartBounds.height + o), this.resizeHandle.includes("n") && (l = this.resizeStartBounds.y + o, h = this.resizeStartBounds.height - o), r = Math.max(10, r), h = Math.max(10, h), e.type) {
|
|
382
420
|
case "RECTANGLE":
|
|
383
421
|
case "IMAGE":
|
|
384
|
-
e.x =
|
|
422
|
+
e.x = a, e.y = l, e.width = r, e.height = h;
|
|
385
423
|
break;
|
|
386
424
|
case "CIRCLE": {
|
|
387
425
|
const d = Math.max(r, h) / 2;
|
|
388
|
-
e.x =
|
|
426
|
+
e.x = a + d, e.y = l + d, e.radius = d;
|
|
389
427
|
break;
|
|
390
428
|
}
|
|
391
429
|
case "TEXT": {
|
|
392
430
|
const d = this.resizeOriginalObject, c = r / this.resizeStartBounds.width;
|
|
393
|
-
e.x =
|
|
431
|
+
e.x = a, e.y = l + h, e.fontSize = Math.max(8, Math.round(d.fontSize * c));
|
|
394
432
|
break;
|
|
395
433
|
}
|
|
396
434
|
case "PATH": {
|
|
397
435
|
const d = this.resizeOriginalObject, c = r / this.resizeStartBounds.width, p = h / this.resizeStartBounds.height;
|
|
398
436
|
e.points = d.points.map((u) => ({
|
|
399
|
-
x:
|
|
437
|
+
x: a + (u.x - this.resizeStartBounds.x) * c,
|
|
400
438
|
y: l + (u.y - this.resizeStartBounds.y) * p
|
|
401
439
|
}));
|
|
402
440
|
break;
|
|
@@ -409,8 +447,8 @@ class v extends HTMLElement {
|
|
|
409
447
|
const e = this.objects.find((n) => n.id === this.selectedId);
|
|
410
448
|
if (e) {
|
|
411
449
|
if (e.type === "PATH") {
|
|
412
|
-
const n = e,
|
|
413
|
-
n.points = n.points.map((l) => ({ x: l.x +
|
|
450
|
+
const n = e, o = i - this.dragStart.x, a = s - this.dragStart.y;
|
|
451
|
+
n.points = n.points.map((l) => ({ x: l.x + o, y: l.y + a })), this.dragStart = { x: i, y: s };
|
|
414
452
|
} else
|
|
415
453
|
e.x = i - this.dragOffset.x, e.y = s - this.dragOffset.y;
|
|
416
454
|
this.renderCanvas(), this.renderMinimap();
|
|
@@ -436,8 +474,8 @@ class v extends HTMLElement {
|
|
|
436
474
|
if (e && e.type === "TEXT") {
|
|
437
475
|
const n = e;
|
|
438
476
|
this.editingTextId = n.id, this.textInputPos = { x: n.x, y: n.y };
|
|
439
|
-
const
|
|
440
|
-
this.showTextInput(
|
|
477
|
+
const o = n.x * this.scale + this.panOffset.x, a = n.y * this.scale + this.panOffset.y;
|
|
478
|
+
this.showTextInput(o, a, n.text), this.setTool("SELECT");
|
|
441
479
|
}
|
|
442
480
|
}
|
|
443
481
|
// 渲染画布
|
|
@@ -499,8 +537,8 @@ class v extends HTMLElement {
|
|
|
499
537
|
{ x: s.x + s.width, y: s.y },
|
|
500
538
|
{ x: s.x, y: s.y + s.height },
|
|
501
539
|
{ x: s.x + s.width, y: s.y + s.height }
|
|
502
|
-
].forEach((
|
|
503
|
-
t.beginPath(), t.rect(
|
|
540
|
+
].forEach((o) => {
|
|
541
|
+
t.beginPath(), t.rect(o.x - e / 2, o.y - e / 2, e, e), t.fill(), t.stroke();
|
|
504
542
|
}), t.strokeStyle = "#3b82f6", t.lineWidth = 1, t.setLineDash([5, 5]), t.strokeRect(s.x, s.y, s.width, s.height), t.setLineDash([]);
|
|
505
543
|
}
|
|
506
544
|
// 渲染小地图
|
|
@@ -508,8 +546,8 @@ class v extends HTMLElement {
|
|
|
508
546
|
if (!this.minimapCtx || !this.config.showMinimap) return;
|
|
509
547
|
const t = this.minimapCanvas, i = this.canvas;
|
|
510
548
|
this.minimapCtx.clearRect(0, 0, t.width, t.height);
|
|
511
|
-
const s = t.width / i.width, e = t.height / i.height, n = Math.min(s, e) * 0.92,
|
|
512
|
-
this.minimapCtx.fillStyle = "#ffffff", this.minimapCtx.fillRect(l, r,
|
|
549
|
+
const s = t.width / i.width, e = t.height / i.height, n = Math.min(s, e) * 0.92, o = i.width * n, a = i.height * n, l = (t.width - o) / 2, r = (t.height - a) / 2;
|
|
550
|
+
this.minimapCtx.fillStyle = "#ffffff", this.minimapCtx.fillRect(l, r, o, a), this.minimapCtx.save(), this.minimapCtx.translate(l, r), this.minimapCtx.scale(n, n), this.minimapCtx.translate(this.panOffset.x, this.panOffset.y), this.minimapCtx.scale(this.scale, this.scale), (this.currentObject ? [...this.objects, this.currentObject] : this.objects).forEach((d) => {
|
|
513
551
|
switch (this.minimapCtx.fillStyle = d.color, this.minimapCtx.strokeStyle = d.color, this.minimapCtx.lineWidth = Math.max(d.lineWidth, 1), this.minimapCtx.setLineDash([]), d.type) {
|
|
514
552
|
case "RECTANGLE": {
|
|
515
553
|
const c = d;
|
|
@@ -537,11 +575,11 @@ class v extends HTMLElement {
|
|
|
537
575
|
break;
|
|
538
576
|
}
|
|
539
577
|
}
|
|
540
|
-
}), this.minimapCtx.restore(), this.minimapCtx.strokeStyle = "#94a3b8", this.minimapCtx.lineWidth = 1, this.minimapCtx.strokeRect(l, r,
|
|
578
|
+
}), this.minimapCtx.restore(), this.minimapCtx.strokeStyle = "#94a3b8", this.minimapCtx.lineWidth = 1, this.minimapCtx.strokeRect(l, r, o, a);
|
|
541
579
|
}
|
|
542
580
|
// 小地图点击定位
|
|
543
581
|
handleMinimapClick(t) {
|
|
544
|
-
const i = this.minimapCanvas.getBoundingClientRect(), s = t.clientX - i.left, e = t.clientY - i.top, n = this.minimapCanvas.width / this.canvas.width,
|
|
582
|
+
const i = this.minimapCanvas.getBoundingClientRect(), s = t.clientX - i.left, e = t.clientY - i.top, n = this.minimapCanvas.width / this.canvas.width, o = this.minimapCanvas.height / this.canvas.height, a = Math.min(n, o) * 0.92, l = this.canvas.width * a, r = this.canvas.height * a, h = (this.minimapCanvas.width - l) / 2, d = (this.minimapCanvas.height - r) / 2, c = s - h, p = e - d, u = (c / a - this.panOffset.x) / this.scale, m = (p / a - this.panOffset.y) / this.scale, g = this.canvas.width / 2, x = this.canvas.height / 2;
|
|
545
583
|
this.panOffset = {
|
|
546
584
|
x: g / this.scale - u,
|
|
547
585
|
y: x / this.scale - m
|
|
@@ -554,11 +592,11 @@ class v extends HTMLElement {
|
|
|
554
592
|
const s = i.files[0], e = new FileReader();
|
|
555
593
|
e.onload = (n) => {
|
|
556
594
|
var l;
|
|
557
|
-
const
|
|
558
|
-
|
|
595
|
+
const o = (l = n.target) == null ? void 0 : l.result, a = new Image();
|
|
596
|
+
a.onload = () => {
|
|
559
597
|
this.saveHistory();
|
|
560
598
|
const r = 300;
|
|
561
|
-
let h =
|
|
599
|
+
let h = a.width, d = a.height;
|
|
562
600
|
if (h > r || d > r) {
|
|
563
601
|
const p = Math.min(r / h, r / d);
|
|
564
602
|
h *= p, d *= p;
|
|
@@ -572,19 +610,19 @@ class v extends HTMLElement {
|
|
|
572
610
|
height: d,
|
|
573
611
|
color: "#000000",
|
|
574
612
|
lineWidth: 1,
|
|
575
|
-
dataUrl:
|
|
576
|
-
imageElement:
|
|
613
|
+
dataUrl: o,
|
|
614
|
+
imageElement: a
|
|
577
615
|
};
|
|
578
616
|
this.objects.push(c), this.selectedId = c.id, this.setTool("SELECT"), this.renderCanvas(), this.renderMinimap(), this.updateUI(), this.dispatchChangeEvent();
|
|
579
|
-
},
|
|
617
|
+
}, a.src = o;
|
|
580
618
|
}, e.readAsDataURL(s), i.value = "";
|
|
581
619
|
}
|
|
582
620
|
// 保存 JSON
|
|
583
621
|
saveJson() {
|
|
584
622
|
const t = {
|
|
585
623
|
version: "1.0",
|
|
586
|
-
objects: this.objects.map((
|
|
587
|
-
const { imageElement:
|
|
624
|
+
objects: this.objects.map((o) => {
|
|
625
|
+
const { imageElement: a, ...l } = o;
|
|
588
626
|
return l;
|
|
589
627
|
})
|
|
590
628
|
}, i = JSON.stringify(t, null, 2), s = new Blob([i], { type: "application/json" }), e = URL.createObjectURL(s), n = document.createElement("a");
|
|
@@ -596,10 +634,10 @@ class v extends HTMLElement {
|
|
|
596
634
|
if (!i.files || i.files.length === 0) return;
|
|
597
635
|
const s = i.files[0], e = new FileReader();
|
|
598
636
|
e.onload = (n) => {
|
|
599
|
-
var
|
|
637
|
+
var o;
|
|
600
638
|
try {
|
|
601
|
-
const
|
|
602
|
-
|
|
639
|
+
const a = JSON.parse((o = n.target) == null ? void 0 : o.result);
|
|
640
|
+
a.objects && Array.isArray(a.objects) && (this.saveHistory(), this.objects = a.objects, this.selectedId = null, this.objects.forEach((l) => {
|
|
603
641
|
if (l.type === "IMAGE" && l.dataUrl) {
|
|
604
642
|
const r = new Image();
|
|
605
643
|
r.onload = () => {
|
|
@@ -607,8 +645,8 @@ class v extends HTMLElement {
|
|
|
607
645
|
}, r.src = l.dataUrl;
|
|
608
646
|
}
|
|
609
647
|
}), this.renderCanvas(), this.renderMinimap(), this.updateUI(), this.dispatchChangeEvent());
|
|
610
|
-
} catch (
|
|
611
|
-
console.error("Failed to load JSON:",
|
|
648
|
+
} catch (a) {
|
|
649
|
+
console.error("Failed to load JSON:", a);
|
|
612
650
|
}
|
|
613
651
|
}, e.readAsText(s), i.value = "";
|
|
614
652
|
}
|
|
@@ -628,7 +666,7 @@ class v extends HTMLElement {
|
|
|
628
666
|
if (this.selectedId) {
|
|
629
667
|
const e = this.objects.find((n) => n.id === this.selectedId);
|
|
630
668
|
if (e) {
|
|
631
|
-
const
|
|
669
|
+
const o = {
|
|
632
670
|
RECTANGLE: "矩形",
|
|
633
671
|
CIRCLE: "圆形",
|
|
634
672
|
PATH: "画笔",
|
|
@@ -636,15 +674,15 @@ class v extends HTMLElement {
|
|
|
636
674
|
IMAGE: "图片"
|
|
637
675
|
}[e.type] || e.type;
|
|
638
676
|
t.innerHTML = `
|
|
639
|
-
<span class="selection-label">已选择: ${
|
|
677
|
+
<span class="selection-label">已选择: ${o}</span>
|
|
640
678
|
<button class="delete-btn" title="删除">
|
|
641
679
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
642
680
|
<path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
|
643
681
|
</svg>
|
|
644
682
|
</button>
|
|
645
683
|
`, t.classList.add("visible");
|
|
646
|
-
const
|
|
647
|
-
|
|
684
|
+
const a = t.querySelector(".delete-btn");
|
|
685
|
+
a && a.addEventListener("click", () => this.deleteSelected());
|
|
648
686
|
}
|
|
649
687
|
} else
|
|
650
688
|
t.classList.remove("visible"), t.innerHTML = "";
|
|
@@ -762,6 +800,7 @@ class v extends HTMLElement {
|
|
|
762
800
|
|
|
763
801
|
<!-- 文本输入 -->
|
|
764
802
|
<div class="text-input-container" style="display: none;">
|
|
803
|
+
<div class="text-input-hint">按 Enter 确认,Esc 取消</div>
|
|
765
804
|
<input type="text" class="text-input" placeholder="输入文本..." />
|
|
766
805
|
</div>
|
|
767
806
|
|
|
@@ -794,10 +833,10 @@ class v extends HTMLElement {
|
|
|
794
833
|
});
|
|
795
834
|
const s = this.shadow.querySelector(".image-input");
|
|
796
835
|
s && s.addEventListener("change", (h) => this.handleImageUpload(h));
|
|
797
|
-
const e = this.shadow.querySelector(".zoom-in-btn"), n = this.shadow.querySelector(".zoom-out-btn"),
|
|
798
|
-
e && e.addEventListener("click", () => this.zoomIn()), n && n.addEventListener("click", () => this.zoomOut()),
|
|
799
|
-
const
|
|
800
|
-
|
|
836
|
+
const e = this.shadow.querySelector(".zoom-in-btn"), n = this.shadow.querySelector(".zoom-out-btn"), o = this.shadow.querySelector(".zoom-text");
|
|
837
|
+
e && e.addEventListener("click", () => this.zoomIn()), n && n.addEventListener("click", () => this.zoomOut()), o && o.addEventListener("click", () => this.resetZoom());
|
|
838
|
+
const a = this.shadow.querySelector(".save-json-btn"), l = this.shadow.querySelector(".load-json-input"), r = this.shadow.querySelector(".export-png-btn");
|
|
839
|
+
a && a.addEventListener("click", () => this.saveJson()), l && l.addEventListener("change", (h) => this.loadJson(h)), r && r.addEventListener("click", () => this.exportPng()), this.minimapCanvas && this.minimapCanvas.addEventListener("click", (h) => this.handleMinimapClick(h)), this.textInput && (this.textInput.addEventListener("keydown", (h) => {
|
|
801
840
|
h.key === "Enter" ? (h.preventDefault(), this.submitText()) : h.key === "Escape" && this.hideTextInput();
|
|
802
841
|
}), this.textInput.addEventListener("blur", () => {
|
|
803
842
|
this.isTextInputVisible && this.submitText();
|
|
@@ -1108,6 +1147,19 @@ class v extends HTMLElement {
|
|
|
1108
1147
|
.text-input-container {
|
|
1109
1148
|
position: absolute;
|
|
1110
1149
|
z-index: 20;
|
|
1150
|
+
display: flex;
|
|
1151
|
+
flex-direction: column;
|
|
1152
|
+
align-items: flex-start;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
.text-input-hint {
|
|
1156
|
+
background: rgba(0, 0, 0, 0.75);
|
|
1157
|
+
color: #fff;
|
|
1158
|
+
font-size: 12px;
|
|
1159
|
+
padding: 4px 8px;
|
|
1160
|
+
border-radius: 4px;
|
|
1161
|
+
margin-bottom: 4px;
|
|
1162
|
+
white-space: nowrap;
|
|
1111
1163
|
}
|
|
1112
1164
|
|
|
1113
1165
|
.text-input {
|
|
@@ -1145,8 +1197,8 @@ class v extends HTMLElement {
|
|
|
1145
1197
|
`;
|
|
1146
1198
|
}
|
|
1147
1199
|
}
|
|
1148
|
-
typeof window < "u" && !customElements.get("canvas-drawing-editor") && customElements.define("canvas-drawing-editor",
|
|
1200
|
+
typeof window < "u" && !customElements.get("canvas-drawing-editor") && customElements.define("canvas-drawing-editor", b);
|
|
1149
1201
|
export {
|
|
1150
|
-
|
|
1202
|
+
b as CanvasDrawingEditor
|
|
1151
1203
|
};
|
|
1152
1204
|
//# sourceMappingURL=canvas-drawing-editor.es.js.map
|