197 lines
4.9 KiB
Markdown
197 lines
4.9 KiB
Markdown
# 图片编辑器移动端适配
|
|
|
|
## 摘要
|
|
为图片编辑器(ImageEditor)添加移动端触摸事件支持,使画笔和文本工具能在移动设备上使用。
|
|
|
|
## 当前状态分析
|
|
|
|
### 核心问题
|
|
Canvas 元素只绑定了鼠标事件,未绑定触摸事件:
|
|
|
|
```tsx
|
|
// 当前实现 - 仅支持鼠标
|
|
<canvas
|
|
onClick={handleCanvasClick} // 鼠标点击
|
|
onMouseDown={handleMouseDown} // 鼠标按下
|
|
onMouseMove={handleMouseMove} // 鼠标移动
|
|
onMouseUp={handleMouseUp} // 鼠标抬起
|
|
/>
|
|
```
|
|
|
|
### 受影响功能
|
|
- **画笔工具**: 无法在触摸屏上绘画
|
|
- **文本工具**: 无法在触摸屏上点击放置文本
|
|
|
|
## 实施步骤
|
|
|
|
### 步骤 1:添加触摸坐标获取函数
|
|
|
|
在 ImageEditor.tsx 中添加通用的位置获取函数:
|
|
|
|
```tsx
|
|
// 获取鼠标或触摸位置
|
|
const getEventPosition = (e: React.MouseEvent<HTMLCanvasElement> | React.TouchEvent<HTMLCanvasElement>) => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return null;
|
|
const rect = canvas.getBoundingClientRect();
|
|
const scaleX = canvas.width / rect.width;
|
|
const scaleY = canvas.height / rect.height;
|
|
|
|
let clientX: number, clientY: number;
|
|
if ('touches' in e) {
|
|
const touch = e.touches[0] || e.changedTouches[0];
|
|
if (!touch) return null;
|
|
clientX = touch.clientX;
|
|
clientY = touch.clientY;
|
|
} else {
|
|
clientX = e.clientX;
|
|
clientY = e.clientY;
|
|
}
|
|
|
|
return {
|
|
x: (clientX - rect.left) * scaleX,
|
|
y: (clientY - rect.top) * scaleY,
|
|
};
|
|
};
|
|
```
|
|
|
|
### 步骤 2:添加触摸事件处理函数
|
|
|
|
```tsx
|
|
// 触摸开始
|
|
const handleTouchStart = (e: React.TouchEvent<HTMLCanvasElement>) => {
|
|
e.preventDefault();
|
|
const pos = getEventPosition(e);
|
|
if (!pos) return;
|
|
|
|
if (activeTool === "brush") {
|
|
setIsDrawing(true);
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return;
|
|
ctx.beginPath();
|
|
ctx.moveTo(pos.x, pos.y);
|
|
ctx.strokeStyle = brushColor;
|
|
ctx.lineWidth = brushSize;
|
|
ctx.lineCap = "round";
|
|
ctx.lineJoin = "round";
|
|
}
|
|
};
|
|
|
|
// 触摸移动
|
|
const handleTouchMove = (e: React.TouchEvent<HTMLCanvasElement>) => {
|
|
e.preventDefault();
|
|
if (!isDrawing || activeTool !== "brush") return;
|
|
const pos = getEventPosition(e);
|
|
if (!pos) return;
|
|
|
|
const canvas = canvasRef.current;
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) return;
|
|
ctx.lineTo(pos.x, pos.y);
|
|
ctx.stroke();
|
|
};
|
|
|
|
// 触摸结束
|
|
const handleTouchEnd = (e: React.TouchEvent<HTMLCanvasElement>) => {
|
|
e.preventDefault();
|
|
if (isDrawing) {
|
|
setIsDrawing(false);
|
|
saveHistory();
|
|
}
|
|
};
|
|
```
|
|
|
|
### 步骤 3:修改文本工具点击处理
|
|
|
|
```tsx
|
|
const handleCanvasTouchTap = (e: React.TouchEvent<HTMLCanvasElement>) => {
|
|
e.preventDefault();
|
|
if (activeTool !== "text" || !textInput.trim()) return;
|
|
const pos = getEventPosition(e);
|
|
if (!pos) return;
|
|
|
|
const newText: TextItem = {
|
|
id: Math.random().toString(36).substring(2, 9),
|
|
text: textInput,
|
|
x: pos.x,
|
|
y: pos.y,
|
|
fontSize: textSize,
|
|
color: textColor,
|
|
};
|
|
setTexts((prev) => [...prev, newText]);
|
|
saveHistory();
|
|
};
|
|
```
|
|
|
|
### 步骤 4:更新 Canvas JSX
|
|
|
|
```tsx
|
|
<canvas
|
|
ref={canvasRef}
|
|
className={`ie-canvas ${activeTool === "text" ? "text-cursor" : ""} ${activeTool === "brush" ? "brush-cursor" : ""}`}
|
|
onClick={handleCanvasClick}
|
|
onMouseDown={handleMouseDown}
|
|
onMouseMove={handleMouseMove}
|
|
onMouseUp={handleMouseUp}
|
|
onMouseLeave={handleMouseUp}
|
|
onTouchStart={handleTouchStart}
|
|
onTouchMove={handleTouchMove}
|
|
onTouchEnd={handleTouchEnd}
|
|
onClick={activeTool === "text" ? handleCanvasClick : undefined}
|
|
onTouchStart={activeTool === "text" ? handleCanvasTouchTap : handleTouchStart}
|
|
/>
|
|
```
|
|
|
|
### 步骤 5:添加 CSS 触摸优化
|
|
|
|
在 ImageEditor.css 中添加:
|
|
|
|
```css
|
|
/* 防止浏览器默认触摸行为 */
|
|
.ie-canvas {
|
|
touch-action: none;
|
|
-webkit-touch-callout: none;
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
/* 移动端画布区域优化 */
|
|
@media (max-width: 768px) {
|
|
.ie-canvas {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.ie-tool-btn {
|
|
min-width: 48px;
|
|
min-height: 48px;
|
|
}
|
|
}
|
|
```
|
|
|
|
## 涉及的文件
|
|
|
|
| 文件 | 操作 |
|
|
|------|------|
|
|
| `src/pages/ImageEditor/ImageEditor.tsx` | 添加触摸事件处理函数和位置获取函数 |
|
|
| `src/pages/ImageEditor/ImageEditor.css` | 添加触摸优化样式 |
|
|
|
|
## 验证步骤
|
|
|
|
1. 运行 TypeScript 编译检查:`npx tsc --noEmit`
|
|
2. 启动开发服务器:`npm run dev`
|
|
3. 在移动设备或浏览器开发者工具中测试:
|
|
- 画笔工具:手指在画布上滑动应该能绘制线条
|
|
- 文本工具:手指点击画布应该能放置文本
|
|
- 其他功能(亮度、滤镜等)应该正常工作
|
|
|
|
## 注意事项
|
|
|
|
- 触摸事件需要 `e.preventDefault()` 阻止默认行为(如页面滚动)
|
|
- `touch-action: none` 防止浏览器处理触摸手势
|
|
- 坐标转换需要考虑 Canvas 的缩放比例
|
|
- 触摸事件使用 `e.touches[0]` 获取第一个触摸点
|