From 39d4df6a74684a9fee7ba5fc0a8ed9b33858d45c Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Wed, 10 Dec 2025 14:32:27 +0100 Subject: [PATCH] Fix zoom race condition and adjust zoom limits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix race condition by using functional state updates in all zoom handlers - Prevents negative or invalid zoom values from rapid scroll events - Change zoom range from 10%-1000% to 10%-200% for more reasonable limits - All zoom operations (wheel, buttons) now safely constrained - Ensures consistent behavior even with fast mouse wheel scrolling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/components/PatternCanvas.tsx | 98 +++++++++++++++++--------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/src/components/PatternCanvas.tsx b/src/components/PatternCanvas.tsx index 8782652..c590b8a 100644 --- a/src/components/PatternCanvas.tsx +++ b/src/components/PatternCanvas.tsx @@ -91,76 +91,80 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat const stage = e.target.getStage(); if (!stage) return; - const oldScale = stage.scaleX(); const pointer = stage.getPointerPosition(); if (!pointer) return; const scaleBy = 1.1; const direction = e.evt.deltaY > 0 ? -1 : 1; - let newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy; - // Apply constraints - newScale = Math.max(0.1, Math.min(10, newScale)); + setStageScale((oldScale) => { + const newScale = Math.max(0.1, Math.min(direction > 0 ? oldScale * scaleBy : oldScale / scaleBy, 2)); - // Zoom towards pointer - const mousePointTo = { - x: (pointer.x - stage.x()) / oldScale, - y: (pointer.y - stage.y()) / oldScale, - }; + // Zoom towards pointer + setStagePos((prevPos) => { + const mousePointTo = { + x: (pointer.x - prevPos.x) / oldScale, + y: (pointer.y - prevPos.y) / oldScale, + }; - const newPos = { - x: pointer.x - mousePointTo.x * newScale, - y: pointer.y - mousePointTo.y * newScale, - }; + return { + x: pointer.x - mousePointTo.x * newScale, + y: pointer.y - mousePointTo.y * newScale, + }; + }); - setStageScale(newScale); - setStagePos(newPos); + return newScale; + }); }, []); // Zoom control handlers const handleZoomIn = useCallback(() => { - const oldScale = stageScale; - const newScale = Math.min(oldScale * 1.2, 10); + setStageScale((oldScale) => { + const newScale = Math.max(0.1, Math.min(oldScale * 1.2, 2)); - // Zoom towards center of viewport - const centerX = containerSize.width / 2; - const centerY = containerSize.height / 2; + // Zoom towards center of viewport + setStagePos((prevPos) => { + const centerX = containerSize.width / 2; + const centerY = containerSize.height / 2; - const mousePointTo = { - x: (centerX - stagePos.x) / oldScale, - y: (centerY - stagePos.y) / oldScale, - }; + const mousePointTo = { + x: (centerX - prevPos.x) / oldScale, + y: (centerY - prevPos.y) / oldScale, + }; - const newPos = { - x: centerX - mousePointTo.x * newScale, - y: centerY - mousePointTo.y * newScale, - }; + return { + x: centerX - mousePointTo.x * newScale, + y: centerY - mousePointTo.y * newScale, + }; + }); - setStageScale(newScale); - setStagePos(newPos); - }, [stageScale, stagePos, containerSize]); + return newScale; + }); + }, [containerSize]); const handleZoomOut = useCallback(() => { - const oldScale = stageScale; - const newScale = Math.max(oldScale / 1.2, 0.1); + setStageScale((oldScale) => { + const newScale = Math.max(0.1, Math.min(oldScale / 1.2, 2)); - // Zoom towards center of viewport - const centerX = containerSize.width / 2; - const centerY = containerSize.height / 2; + // Zoom towards center of viewport + setStagePos((prevPos) => { + const centerX = containerSize.width / 2; + const centerY = containerSize.height / 2; - const mousePointTo = { - x: (centerX - stagePos.x) / oldScale, - y: (centerY - stagePos.y) / oldScale, - }; + const mousePointTo = { + x: (centerX - prevPos.x) / oldScale, + y: (centerY - prevPos.y) / oldScale, + }; - const newPos = { - x: centerX - mousePointTo.x * newScale, - y: centerY - mousePointTo.y * newScale, - }; + return { + x: centerX - mousePointTo.x * newScale, + y: centerY - mousePointTo.y * newScale, + }; + }); - setStageScale(newScale); - setStagePos(newPos); - }, [stageScale, stagePos, containerSize]); + return newScale; + }); + }, [containerSize]); const handleZoomReset = useCallback(() => { const initialScale = initialScaleRef.current;