如何基于react-pdf Canvas实现工程图纸的动态鼠标绘图?
在react-pdf Canvas组件上实现鼠标交互绘图(直线/正方形)
要实现鼠标驱动的绘图功能,核心是通过监听鼠标事件管理绘图状态,再结合react-pdf的Canvas组件动态渲染图形。以下是具体实现方案:
1. 基础状态管理
首先用React状态记录绘图关键信息:
- 绘图状态(是否正在绘制)
- 起始点坐标
- 当前鼠标位置
- 已完成的图形列表
- 当前选中的绘图工具(直线/正方形)
import { useState, useRef } from 'react'; import { Canvas } from '@react-pdf/renderer'; const PdfAnnotator = () => { const [isDrawing, setIsDrawing] = useState(false); const [startPoint, setStartPoint] = useState({ x: 0, y: 0 }); const [currentPoint, setCurrentPoint] = useState({ x: 0, y: 0 }); const [shapes, setShapes] = useState([]); const [tool, setTool] = useState('line'); // 可选值:'line' | 'rectangle' const canvasRef = useRef(null); // 后续事件处理和绘图逻辑 };
2. 监听鼠标事件
在Canvas外层包裹容器,监听鼠标按下、移动、松开事件,同时处理DOM坐标到PDF Canvas坐标的转换:
// 把鼠标的DOM坐标转为PDF Canvas的相对坐标 const getCanvasRelativeCoords = (e) => { const canvas = canvasRef.current; if (!canvas) return { x: 0, y: 0 }; const rect = canvas.getBoundingClientRect(); // 若PDF有缩放,需调整此处scale系数匹配缩放比例 const scale = 1; return { x: (e.clientX - rect.left) * scale, y: (e.clientY - rect.top) * scale, }; }; // 鼠标按下:启动绘图流程 const handleMouseDown = (e) => { const coords = getCanvasRelativeCoords(e); setIsDrawing(true); setStartPoint(coords); setCurrentPoint(coords); }; // 鼠标移动:更新实时绘图位置 const handleMouseMove = (e) => { if (!isDrawing) return; const coords = getCanvasRelativeCoords(e); setCurrentPoint(coords); }; // 鼠标松开:完成当前图形并保存 const handleMouseUp = () => { if (!isDrawing) return; setShapes([ ...shapes, { type: tool, start: startPoint, end: currentPoint, color: 'red', }, ]); setIsDrawing(false); };
3. 动态渲染图形
在Canvas的paint函数中,先绘制已保存的所有图形,再绘制当前正在绘制的临时图形:
const renderShapes = (painter) => { // 绘制已完成的图形 shapes.forEach((shape) => { painter.save(); painter.stroke(shape.color); painter.lineWidth(2); if (shape.type === 'line') { painter.moveTo(shape.start.x, shape.start.y); painter.lineTo(shape.end.x, shape.end.y); } else if (shape.type === 'rectangle') { const width = shape.end.x - shape.start.x; const height = shape.end.y - shape.start.y; painter.rect(shape.start.x, shape.start.y, width, height); } painter.stroke(); painter.restore(); }); // 绘制临时的正在绘制的图形(虚线区分) if (isDrawing) { painter.save(); painter.stroke('blue'); painter.lineWidth(2); painter.setLineDash([5, 5]); if (tool === 'line') { painter.moveTo(startPoint.x, startPoint.y); painter.lineTo(currentPoint.x, currentPoint.y); } else if (tool === 'rectangle') { const width = currentPoint.x - startPoint.x; const height = currentPoint.y - startPoint.y; painter.rect(startPoint.x, startPoint.y, width, height); } painter.stroke(); painter.restore(); } }; return ( <div style={{ position: 'relative' }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp}> <Canvas ref={canvasRef} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} paint={(painter) => renderShapes(painter)} /> {/* 可选:添加工具切换按钮 */} <div style={{ position: 'absolute', bottom: 10, left: 10 }}> <button onClick={() => setTool('line')} style={{ marginRight: 10 }}>直线</button> <button onClick={() => setTool('rectangle')}>正方形</button> </div> </div> );
4. 测量功能扩展
如果需要计算图形实际尺寸,可利用保存的坐标点计算。例如计算直线长度:
// 计算两点间距离(基于PDF坐标,需结合PDF实际尺寸转换为物理单位) const calculateDistance = (point1, point2) => { const dx = point2.x - point1.x; const dy = point2.y - point1.y; return Math.sqrt(dx * dx + dy * dy); }; // 在渲染直线时添加长度标注示例 if (shape.type === 'line') { const midX = (shape.start.x + shape.end.x) / 2; const midY = (shape.start.y + shape.end.y) / 2; const distance = calculateDistance(shape.start, shape.end); painter.fillText(`${distance.toFixed(2)} pt`, midX, midY); }
关键注意事项
- 坐标转换:若PDF页面有缩放,需调整
scale系数确保鼠标位置与Canvas坐标匹配。 - 性能优化:图形数量较多时,可使用
useMemo缓存绘制逻辑,避免不必要重绘。 - 样式隔离:确保
Canvas与外层容器的定位样式正确(如绝对定位),防止鼠标事件偏移。
内容的提问来源于stack exchange,提问作者Abed Aarabi
相关产品推荐
相关产品推荐

