From e79fb9b34a74b08358fc78a673197e736b49ba6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=87=A4=E5=90=89?= Date: Mon, 10 Mar 2025 14:55:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E8=A7=A6=E6=91=B8=E4=BA=8B=E4=BB=B6=E4=B8=8E=E5=8F=8C=E6=8C=87?= =?UTF-8?q?=E7=BC=A9=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../measure/components/konva/MeasurementCanvas.tsx | 341 +++++++++------------ 1 file changed, 143 insertions(+), 198 deletions(-) diff --git a/src/pages/measure/components/konva/MeasurementCanvas.tsx b/src/pages/measure/components/konva/MeasurementCanvas.tsx index b629c2e..0ed2249 100644 --- a/src/pages/measure/components/konva/MeasurementCanvas.tsx +++ b/src/pages/measure/components/konva/MeasurementCanvas.tsx @@ -63,9 +63,9 @@ export interface MeasurementCanvasProps { initialMeasurementDataLeft?: Point[]; initialMeasurementDataRight?: Point[]; initialAnalysisData?: AnalysisData[]; - // 新增属性:控制是否显示标准线(benchmark shapes) + // 控制是否显示标准线(benchmark shapes) showBenchmark?: boolean; - // 新增属性:控制是否显示分析线 + // 控制是否显示分析线 showAnalysis?: boolean; } @@ -80,6 +80,13 @@ export interface MeasurementCanvasRef { redraw: () => void; } +interface PinchData { + initialDistance: number; + initialScale: number; + initialOffset: { x: number; y: number }; + initialCenter: Point; // 固定的缩放中心 +} + const MeasurementCanvas = forwardRef( (props, ref) => { const { @@ -100,19 +107,18 @@ const MeasurementCanvas = forwardRef pixelPerMm ? computedScale : pixelPerMm; - const logicalCenter = { x: (logicalExtent.minX + logicalExtent.maxX) / 2, y: (logicalExtent.minY + logicalExtent.maxY) / 2, @@ -127,28 +133,17 @@ const MeasurementCanvas = forwardRef(initialOffset); const [scale, setScale] = useState(initialScale); - const [benchmarkData, setBenchmarkData] = - useState(initialBenchmarkData); - const [analysisData, setAnalysisData] = - useState(initialAnalysisData); + const [benchmarkData, setBenchmarkData] = useState(initialBenchmarkData); + const [analysisData, setAnalysisData] = useState(initialAnalysisData); - // 左右测量数据使用定时器更新 + // 定时更新测量数据(左右两侧) const leftPointsRef = useRef([...initialMeasurementDataLeft]); const rightPointsRef = useRef([...initialMeasurementDataRight]); - - const [measurementDataLeft, setMeasurementDataLeftState] = useState( - initialMeasurementDataLeft - ); - const [measurementDataRight, setMeasurementDataRightState] = useState( - initialMeasurementDataRight - ); - - // 新增直接绘制的测量数据状态,不使用定时器 + const [measurementDataLeft, setMeasurementDataLeftState] = useState(initialMeasurementDataLeft); + const [measurementDataRight, setMeasurementDataRightState] = useState(initialMeasurementDataRight); const [measurementData, setMeasurementDataState] = useState([]); - const refreshInterval = 50; const refreshTimer = useRef(null); - useEffect(() => { if (!refreshTimer.current) { refreshTimer.current = window.setInterval(() => { @@ -199,80 +194,122 @@ const MeasurementCanvas = forwardRef(null); - const transform = (pt: Point) => ({ - x: canvasCenter.x + offset.x + (pt.x - origin.x) * scale, - y: canvasCenter.y + offset.y + (pt.y - origin.y) * scale, - }); - - const clampOffset = (newOffset: { x: number; y: number }, currentScale: number) => { - const left = canvasCenter.x + newOffset.x + (logicalExtent.minX - origin.x) * currentScale; - const right = canvasCenter.x + newOffset.x + (logicalExtent.maxX - origin.x) * currentScale; - const top = canvasCenter.y + newOffset.y + (logicalExtent.minY - origin.y) * currentScale; - const bottom = canvasCenter.y + newOffset.y + (logicalExtent.maxY - origin.y) * currentScale; - let clampedX = newOffset.x; - let clampedY = newOffset.y; - if (left > 0) clampedX -= left; - if (right < width) clampedX += width - right; - if (top > 0) clampedY -= top; - if (bottom < height) clampedY += height - bottom; - return { x: clampedX, y: clampedY }; + // 记录当前活跃 pointer(pointerId -> Point) + const pointersRef = useRef>(new Map()); + // 单指拖拽的上一次位置 + const lastDragPosRef = useRef(null); + // 针对鼠标,记录是否按下 + const isDraggingRef = useRef(false); + // 记录双指缩放的初始数据(只在两指刚开始时记录一次) + const pinchDataRef = useRef(null); + + // 辅助函数:从事件中获取正确的指针位置 + const getPointerPosition = (e: any): Point => { + const pos = stageRef.current.getPointerPosition(); + return pos ? pos : { x: e.evt.clientX, y: e.evt.clientY }; }; - const isDragging = useRef(false); - const lastPos = useRef<{ x: number; y: number } | null>(null); - const dragFrame = useRef(null); - - const handleMouseDown = () => { - isDragging.current = true; - lastPos.current = stageRef.current.getPointerPosition(); + const handlePointerDown = (e: any) => { + const point = getPointerPosition(e); + const pointerId = e.evt.pointerId; + pointersRef.current.set(pointerId, point); + // 判断是否为触摸事件 + const isTouch = e.evt.touches && e.evt.touches.length > 0; + if (!isTouch) { + isDraggingRef.current = true; + } + if (pointersRef.current.size === 1) { + lastDragPosRef.current = point; + } + // 当检测到两个手指时,记录初始数据——固定缩放中心 + if (pointersRef.current.size === 2) { + const pts = Array.from(pointersRef.current.values()); + const initialCenter = { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 }; + const initialDistance = Math.hypot(pts[1].x - pts[0].x, pts[1].y - pts[0].y); + pinchDataRef.current = { + initialDistance, + initialScale: scale, + initialOffset: offset, + initialCenter, // 固定的缩放中心 + }; + } }; - const handleMouseMove = () => { - if (!isDragging.current) return; - const currentPos = stageRef.current.getPointerPosition(); - if (lastPos.current && currentPos) { - if (!dragFrame.current) { - dragFrame.current = requestAnimationFrame(() => { - const last = lastPos.current!; - const newOff = { - x: offset.x + currentPos.x - last.x, - y: offset.y + currentPos.y - last.y, - }; - setOffset(clampOffset(newOff, scale)); - lastPos.current = currentPos; - dragFrame.current = null; - }); + const handlePointerMove = (e: any) => { + const isTouch = e.evt.touches && e.evt.touches.length > 0; + if (!isTouch && !isDraggingRef.current) { + return; + } + const pointerId = e.evt.pointerId; + const point = getPointerPosition(e); + pointersRef.current.set(pointerId, point); + if (pointersRef.current.size === 1) { + // 单指拖拽 + if (lastDragPosRef.current) { + const dx = point.x - lastDragPosRef.current.x; + const dy = point.y - lastDragPosRef.current.y; + setOffset((prev) => clampOffset({ x: prev.x + dx, y: prev.y + dy }, scale)); } + lastDragPosRef.current = point; + } else if (pointersRef.current.size >= 2 && pinchDataRef.current) { + // 双指缩放:固定缩放中心,与鼠标滚轮缩放逻辑一致 + const pts = Array.from(pointersRef.current.values()); + const currentDistance = Math.hypot(pts[1].x - pts[0].x, pts[1].y - pts[0].y); + const { initialDistance, initialScale, initialOffset, initialCenter } = pinchDataRef.current; + const scaleFactor = currentDistance / initialDistance; + let newScale = initialScale * scaleFactor; + newScale = Math.max(minZoom * pixelPerMm, Math.min(newScale, maxZoom * pixelPerMm)); + // 计算逻辑坐标 L:与鼠标滚轮缩放公式一致,固定 pointer 为初始记录的 initialCenter + const L = { + x: origin.x + (initialCenter.x - (canvasCenter.x + initialOffset.x)) / initialScale, + y: origin.y + (initialCenter.y - (canvasCenter.y + initialOffset.y)) / initialScale, + }; + // 计算新的 offset + const newOffset = { + x: initialCenter.x - canvasCenter.x - (L.x - origin.x) * newScale, + y: initialCenter.y - canvasCenter.y - (L.y - origin.y) * newScale, + }; + setScale(newScale); + // 此处调用 clampOffset 确保缩放过程中不会超出边界 + setOffset(clampOffset(newOffset, newScale)); } }; - const handleMouseUp = () => { - isDragging.current = false; - if (dragFrame.current) { - cancelAnimationFrame(dragFrame.current); - dragFrame.current = null; + const handlePointerUp = (e: any) => { + const pointerId = e.evt.pointerId; + pointersRef.current.delete(pointerId); + const isTouch = e.evt.touches && e.evt.touches.length > 0; + if (!isTouch) { + isDraggingRef.current = false; + } + // 当手指数不足2时重置双指缩放初始数据 + if (pointersRef.current.size < 2) { + pinchDataRef.current = null; + } + if (pointersRef.current.size === 1) { + const remaining = Array.from(pointersRef.current.values())[0]; + lastDragPosRef.current = remaining; + } else if (pointersRef.current.size === 0) { + lastDragPosRef.current = null; } }; - const handleMouseLeave = () => { - isDragging.current = false; - if (dragFrame.current) { - cancelAnimationFrame(dragFrame.current); - dragFrame.current = null; - } + const handlePointerCancel = (e: any) => { + handlePointerUp(e); }; + // 鼠标滚轮缩放(仅适用于 PC) const handleWheel = (e: any) => { e.evt.preventDefault(); const oldScale = scale; const pointer = stageRef.current.getPointerPosition(); + if (!pointer) return; const L = { x: origin.x + (pointer.x - (canvasCenter.x + offset.x)) / oldScale, y: origin.y + (pointer.y - (canvasCenter.y + offset.y)) / oldScale, }; let newScale = e.evt.deltaY < 0 ? oldScale * 1.1 : oldScale / 1.1; - if (newScale < minZoom * pixelPerMm) newScale = minZoom * pixelPerMm; - if (newScale > maxZoom * pixelPerMm) newScale = maxZoom * pixelPerMm; + newScale = Math.max(minZoom * pixelPerMm, Math.min(newScale, maxZoom * pixelPerMm)); const newOffset = { x: pointer.x - canvasCenter.x - (L.x - origin.x) * newScale, y: pointer.y - canvasCenter.y - (L.y - origin.y) * newScale, @@ -281,69 +318,27 @@ const MeasurementCanvas = forwardRef(null); - const handleTouchStart = (e: any) => { - const touches = e.evt.touches; - if (touches && touches.length === 2) { - e.evt.preventDefault(); - const [t1, t2] = touches; - const dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY); - lastTouchDistance.current = dist; - } else if (touches && touches.length === 1) { - isDragging.current = true; - lastPos.current = { x: touches[0].clientX, y: touches[0].clientY }; - } - }; - - const handleTouchMove = (e: any) => { - const touches = e.evt.touches; - if (touches && touches.length === 2) { - e.evt.preventDefault(); - const [t1, t2] = touches; - const dist = Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY); - if (lastTouchDistance.current) { - const delta = dist / lastTouchDistance.current; - let newScale = scale * delta; - if (newScale < minZoom * pixelPerMm) newScale = minZoom * pixelPerMm; - if (newScale > maxZoom * pixelPerMm) newScale = maxZoom * pixelPerMm; - const center = { - x: (t1.clientX + t2.clientX) / 2, - y: (t1.clientY + t2.clientY) / 2, - }; - const L = { - x: origin.x + (center.x - (canvasCenter.x + offset.x)) / scale, - y: origin.y + (center.y - (canvasCenter.y + offset.y)) / scale, - }; - const newOffset = { - x: center.x - canvasCenter.x - (L.x - origin.x) * newScale, - y: center.y - canvasCenter.y - (L.y - origin.y) * newScale, - }; - setScale(newScale); - setOffset(clampOffset(newOffset, newScale)); - lastTouchDistance.current = dist; - } - } else if (touches && touches.length === 1 && isDragging.current) { - const t = touches[0]; - const pos = { x: t.clientX, y: t.clientY }; - if (lastPos.current) { - setOffset((prev) => - clampOffset( - { - x: prev.x + pos.x - lastPos.current!.x, - y: prev.y + pos.y - lastPos.current!.y, - }, - scale - ) - ); - } - lastPos.current = pos; - } + // 辅助函数:将逻辑坐标转换为屏幕坐标 + const transform = (pt: Point | null): { x: number; y: number } => { + if (!pt) return { x: 0, y: 0 }; + return { + x: canvasCenter.x + offset.x + (pt.x - origin.x) * scale, + y: canvasCenter.y + offset.y + (pt.y - origin.y) * scale, + }; }; - const handleTouchEnd = () => { - isDragging.current = false; - lastTouchDistance.current = null; - lastPos.current = null; + const clampOffset = (newOffset: { x: number; y: number }, currentScale: number) => { + const left = canvasCenter.x + newOffset.x + (logicalExtent.minX - origin.x) * currentScale; + const right = canvasCenter.x + newOffset.x + (logicalExtent.maxX - origin.x) * currentScale; + const top = canvasCenter.y + newOffset.y + (logicalExtent.minY - origin.y) * currentScale; + const bottom = canvasCenter.y + newOffset.y + (logicalExtent.maxY - origin.y) * currentScale; + let clampedX = newOffset.x; + let clampedY = newOffset.y; + if (left > 0) clampedX -= left; + if (right < width) clampedX += width - right; + if (top > 0) clampedY -= top; + if (bottom < height) clampedY += height - bottom; + return { x: clampedX, y: clampedY }; }; const renderGridAndAxes = () => { @@ -352,45 +347,25 @@ const MeasurementCanvas = forwardRef + ); } for (let y = logicalExtent.minY; y <= logicalExtent.maxY; y += gridStep) { const p1 = transform({ x: logicalExtent.minX, y }); const p2 = transform({ x: logicalExtent.maxX, y }); lines.push( - + ); } const xAxisStart = transform({ x: logicalExtent.minX, y: 0 }); const xAxisEnd = transform({ x: logicalExtent.maxX, y: 0 }); lines.push( - + ); const yAxisStart = transform({ x: 0, y: logicalExtent.minY }); const yAxisEnd = transform({ x: 0, y: logicalExtent.maxY }); lines.push( - + ); return lines; }; @@ -400,31 +375,16 @@ const MeasurementCanvas = forwardRef + ); } for (let y = logicalExtent.minY; y <= logicalExtent.maxY; y += dynamicYInterval) { const pos = transform({ x: 0, y }); texts.push( - + ); } return texts; @@ -474,9 +434,7 @@ const MeasurementCanvas = forwardRef - Math.PI, + (normalize(endAngle) - normalize(startAngle) + 2 * Math.PI) % (2 * Math.PI) > Math.PI, }; return ( - - + + ); }); @@ -600,14 +548,11 @@ const MeasurementCanvas = forwardRef