271 lines
7.3 KiB
Markdown
271 lines
7.3 KiB
Markdown
|
|
# Konva 到 DOM 渲染迁移文档
|
|||
|
|
|
|||
|
|
## 📋 迁移概述
|
|||
|
|
|
|||
|
|
### 迁移原因
|
|||
|
|
微信小程序不支持 Konva.js,需要将基于 Canvas 的渲染方案改为纯 DOM + SVG 渲染方案。
|
|||
|
|
|
|||
|
|
### 迁移目标
|
|||
|
|
- ✅ 保留所有现有功能
|
|||
|
|
- ✅ 使用 Vue 响应式系统管理渲染
|
|||
|
|
- ✅ 保持命令模式的撤销/重做功能
|
|||
|
|
- ✅ 兼容微信小程序环境
|
|||
|
|
|
|||
|
|
## 🔄 核心变更
|
|||
|
|
|
|||
|
|
### 1. 新增文件
|
|||
|
|
|
|||
|
|
#### `useMarkingDom.ts`
|
|||
|
|
替代 `useMarkingKonva.ts`,使用 DOM 渲染:
|
|||
|
|
- **数据结构**: `DomMarkingData` (兼容原 `KonvaMarkingData`)
|
|||
|
|
- **工具枚举**: `MarkingTool` (保持不变)
|
|||
|
|
- **核心函数**: `useSimpleDomLayer` 替代 `useSimpleKonvaLayer`
|
|||
|
|
|
|||
|
|
**主要特性**:
|
|||
|
|
- 使用 Vue 响应式系统自动渲染
|
|||
|
|
- 鼠标/触摸事件处理
|
|||
|
|
- 坐标转换(支持缩放和偏移)
|
|||
|
|
- 碰撞检测(用于擦除功能)
|
|||
|
|
|
|||
|
|
#### `DomImageRenderer.vue`
|
|||
|
|
替代 `KonvaImageRenderer.vue`,使用 DOM 渲染:
|
|||
|
|
- **背景图片**: `<img>` 标签
|
|||
|
|
- **矩形**: `<div>` + CSS border
|
|||
|
|
- **线条**: `<svg>` + `<polyline>`
|
|||
|
|
- **特殊标记**: `<svg>` 图标
|
|||
|
|
- **文本**: `<div>` + CSS
|
|||
|
|
|
|||
|
|
**优势**:
|
|||
|
|
- 无需 Canvas API
|
|||
|
|
- 完全兼容小程序
|
|||
|
|
- Vue 自动管理 DOM 更新
|
|||
|
|
- 更好的性能(小数据量场景)
|
|||
|
|
|
|||
|
|
### 2. 修改文件
|
|||
|
|
|
|||
|
|
#### `useMarkingCommand.ts`
|
|||
|
|
- 将类型从 `KonvaMarkingData` 改为 `BaseMarkingData`
|
|||
|
|
- 将 `KonvaShape` 改为 `BaseShape`
|
|||
|
|
- 移除对 `layer` 的依赖(传入 `null`)
|
|||
|
|
- 保持命令模式逻辑不变
|
|||
|
|
|
|||
|
|
#### `QuestionRenderer.vue`
|
|||
|
|
- 导入 `DomImageRenderer` 替代 `KonvaImageRenderer`
|
|||
|
|
- 导入 `useMarkingDom` 类型替代 `useMarkingKonva`
|
|||
|
|
- 其他逻辑保持不变
|
|||
|
|
|
|||
|
|
#### `MarkingImageViewerNew.vue`
|
|||
|
|
- 更新类型导入:`DomMarkingData` 替代 `KonvaMarkingData`
|
|||
|
|
|
|||
|
|
#### `ReviewImageRenderer.vue`
|
|||
|
|
- 使用 `DomImageRenderer` 替代 `KonvaImageRenderer`
|
|||
|
|
- 只读模式渲染历史标记
|
|||
|
|
|
|||
|
|
#### `TraceToolbar.vue`
|
|||
|
|
- 更新导入:从 `useMarkingDom` 导入 `MarkingTool`
|
|||
|
|
|
|||
|
|
### 3. 保留文件(兼容性)
|
|||
|
|
|
|||
|
|
#### `KonvaImageRenderer.vue`
|
|||
|
|
保留但不再使用,可用于对比测试
|
|||
|
|
|
|||
|
|
#### `useMarkingKonva.ts`
|
|||
|
|
保留但不再使用,可用于对比测试
|
|||
|
|
|
|||
|
|
#### `useMarkingTools.ts`
|
|||
|
|
标记为 `@deprecated`,仅用于 Konva 兼容
|
|||
|
|
|
|||
|
|
## 🎯 功能对照表
|
|||
|
|
|
|||
|
|
| 功能 | Konva 实现 | DOM 实现 | 状态 |
|
|||
|
|
|------|-----------|---------|------|
|
|||
|
|
| 绘制矩形 | Konva.Rect | `<div>` + border | ✅ |
|
|||
|
|
| 绘制线条 | Konva.Line | `<svg><polyline>` | ✅ |
|
|||
|
|
| 特殊标记 | Konva.Group | `<svg>` 图标 | ✅ |
|
|||
|
|
| 文本注释 | Konva.Text | `<div>` | ✅ |
|
|||
|
|
| 擦除工具 | layer.getIntersection() | 碰撞检测算法 | ✅ |
|
|||
|
|
| 撤销/重做 | 命令模式 | 命令模式 | ✅ |
|
|||
|
|
| 缩放 | layer.scale() | CSS transform | ✅ |
|
|||
|
|
| 自适应宽度 | stage.size() | CSS 百分比 | ✅ |
|
|||
|
|
| 触摸事件 | Konva 事件 | 原生事件 | ✅ |
|
|||
|
|
| 只读模式 | 不绑定事件 | 不绑定事件 | ✅ |
|
|||
|
|
|
|||
|
|
## 🔍 技术细节
|
|||
|
|
|
|||
|
|
### 坐标转换
|
|||
|
|
|
|||
|
|
**Konva 方式**:
|
|||
|
|
```typescript
|
|||
|
|
const pos = stage.getPointerPosition()
|
|||
|
|
const relativePos = {
|
|||
|
|
x: (pos.x - layer.x()) / scale - offset.x,
|
|||
|
|
y: (pos.y - layer.y()) / scale - offset.y,
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**DOM 方式**:
|
|||
|
|
```typescript
|
|||
|
|
const rect = container.getBoundingClientRect()
|
|||
|
|
const relativePos = {
|
|||
|
|
x: (clientX - rect.left) / scale - offset.x,
|
|||
|
|
y: (clientY - rect.top) / scale - offset.y,
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 碰撞检测
|
|||
|
|
|
|||
|
|
**擦除功能的核心**:判断点击位置是否在元素内
|
|||
|
|
|
|||
|
|
#### 特殊标记(20x20 正方形)
|
|||
|
|
```typescript
|
|||
|
|
const isPointInSpecialMark = (pos, mark) => {
|
|||
|
|
const size = 20
|
|||
|
|
return pos.x >= mark.x - size/2 && pos.x <= mark.x + size/2
|
|||
|
|
&& pos.y >= mark.y - size/2 && pos.y <= mark.y + size/2
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 矩形
|
|||
|
|
```typescript
|
|||
|
|
const isPointInRect = (pos, rect) => {
|
|||
|
|
return pos.x >= rect.x && pos.x <= rect.x + rect.width
|
|||
|
|
&& pos.y >= rect.y && pos.y <= rect.y + rect.height
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 线条(点到线段距离)
|
|||
|
|
```typescript
|
|||
|
|
const isPointInLine = (pos, line) => {
|
|||
|
|
const threshold = strokeWidth + 5
|
|||
|
|
for (let i = 0; i < points.length - 2; i += 2) {
|
|||
|
|
const distance = pointToLineDistance(pos, p1, p2)
|
|||
|
|
if (distance < threshold) return true
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 渲染方式
|
|||
|
|
|
|||
|
|
**Konva**: 手动管理 Konva 节点
|
|||
|
|
```typescript
|
|||
|
|
const rect = new Konva.Rect({ x, y, width, height })
|
|||
|
|
layer.add(rect)
|
|||
|
|
layer.draw()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**DOM**: Vue 响应式自动渲染
|
|||
|
|
```vue
|
|||
|
|
<div
|
|||
|
|
v-for="shape in markingData.shapes"
|
|||
|
|
:key="shape.id"
|
|||
|
|
:style="{
|
|||
|
|
left: `${shape.x}px`,
|
|||
|
|
top: `${shape.y}px`,
|
|||
|
|
width: `${shape.width}px`,
|
|||
|
|
height: `${shape.height}px`,
|
|||
|
|
}"
|
|||
|
|
/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ✅ 测试清单
|
|||
|
|
|
|||
|
|
### 基础绘制功能
|
|||
|
|
- [ ] 绘制矩形
|
|||
|
|
- [ ] 绘制线条(笔工具)
|
|||
|
|
- [ ] 添加文本注释
|
|||
|
|
- [ ] 添加正确标记 ✓
|
|||
|
|
- [ ] 添加错误标记 ✗
|
|||
|
|
- [ ] 添加半对标记
|
|||
|
|
|
|||
|
|
### 交互功能
|
|||
|
|
- [ ] 擦除工具(点击擦除)
|
|||
|
|
- [ ] 擦除工具(拖动擦除)
|
|||
|
|
- [ ] 撤销操作
|
|||
|
|
- [ ] 重做操作
|
|||
|
|
- [ ] 清空所有标记
|
|||
|
|
|
|||
|
|
### 缩放功能
|
|||
|
|
- [ ] 手动缩放
|
|||
|
|
- [ ] 自适应宽度模式
|
|||
|
|
- [ ] 双指缩放(触摸设备)
|
|||
|
|
|
|||
|
|
### 快捷打分
|
|||
|
|
- [ ] 快捷打分点击模式
|
|||
|
|
- [ ] 加分模式
|
|||
|
|
- [ ] 减分模式
|
|||
|
|
- [ ] 边界检查
|
|||
|
|
|
|||
|
|
### 特殊场景
|
|||
|
|
- [ ] 多图片渲染
|
|||
|
|
- [ ] 只读模式(历史记录查看)
|
|||
|
|
- [ ] 横屏/竖屏切换
|
|||
|
|
- [ ] 数据导入/导出
|
|||
|
|
|
|||
|
|
### 性能测试
|
|||
|
|
- [ ] 大量标记渲染(100+)
|
|||
|
|
- [ ] 快速连续绘制
|
|||
|
|
- [ ] 内存泄漏检查
|
|||
|
|
|
|||
|
|
## 🐛 已知问题
|
|||
|
|
|
|||
|
|
### 1. 触摸事件兼容性
|
|||
|
|
**问题**: 小程序环境的触摸事件可能与浏览器不同
|
|||
|
|
**解决**: 同时监听 `mousedown/touchstart` 等事件
|
|||
|
|
|
|||
|
|
### 2. SVG 渲染性能
|
|||
|
|
**问题**: 大量 SVG 元素可能影响性能
|
|||
|
|
**优化**:
|
|||
|
|
- 合并相同类型的 SVG 到一个容器
|
|||
|
|
- 使用虚拟滚动(如果需要)
|
|||
|
|
|
|||
|
|
### 3. 文本宽度估算
|
|||
|
|
**问题**: 擦除功能需要估算文本宽度
|
|||
|
|
**当前方案**: `width ≈ text.length * fontSize * 0.6`
|
|||
|
|
**改进**: 可使用 Canvas measureText 或 DOM getBoundingClientRect
|
|||
|
|
|
|||
|
|
## 📝 迁移步骤
|
|||
|
|
|
|||
|
|
### 对于新功能
|
|||
|
|
直接使用 `DomImageRenderer` 和 `useMarkingDom`
|
|||
|
|
|
|||
|
|
### 对于现有功能
|
|||
|
|
1. 替换导入语句
|
|||
|
|
2. 更新类型定义
|
|||
|
|
3. 测试功能完整性
|
|||
|
|
4. 删除旧的 Konva 相关代码(可选)
|
|||
|
|
|
|||
|
|
## 🔗 相关文件
|
|||
|
|
|
|||
|
|
### 核心文件
|
|||
|
|
- `src/components/marking/composables/renderer/useMarkingDom.ts`
|
|||
|
|
- `src/components/marking/components/renderer/DomImageRenderer.vue`
|
|||
|
|
- `src/components/marking/composables/renderer/useMarkingCommand.ts`
|
|||
|
|
|
|||
|
|
### 使用示例
|
|||
|
|
- `src/components/marking/components/renderer/QuestionRenderer.vue`
|
|||
|
|
- `src/components/marking/components/renderer/MarkingImageViewerNew.vue`
|
|||
|
|
- `src/components/marking/review/ReviewImageRenderer.vue`
|
|||
|
|
|
|||
|
|
### 工具栏
|
|||
|
|
- `src/components/marking/components/renderer/TraceToolbar.vue`
|
|||
|
|
|
|||
|
|
## 📚 参考资料
|
|||
|
|
|
|||
|
|
### Vue 响应式渲染
|
|||
|
|
- [Vue 3 响应式基础](https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html)
|
|||
|
|
- [Vue 3 列表渲染](https://cn.vuejs.org/guide/essentials/list.html)
|
|||
|
|
|
|||
|
|
### SVG 绘图
|
|||
|
|
- [MDN SVG 教程](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial)
|
|||
|
|
- [SVG Path 命令](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths)
|
|||
|
|
|
|||
|
|
### 触摸事件
|
|||
|
|
- [MDN Touch Events](https://developer.mozilla.org/zh-CN/docs/Web/API/Touch_events)
|
|||
|
|
- [小程序触摸事件](https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html)
|
|||
|
|
|
|||
|
|
## 🎉 总结
|
|||
|
|
|
|||
|
|
本次迁移成功将 Konva Canvas 渲染改为 DOM + SVG 渲染,完全兼容微信小程序环境,同时保留了所有原有功能。使用 Vue 响应式系统大大简化了渲染逻辑,提高了代码可维护性。
|
|||
|
|
|