xlx_teacher_app/.task/konva-to-dom-migration.md

271 lines
7.3 KiB
Markdown
Raw Permalink Normal View History

2025-11-05 20:02:46 +08:00
# 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 响应式系统大大简化了渲染逻辑,提高了代码可维护性。