From 8b66944e52f6bc158bf5703ce31744bb4a7db0d4 Mon Sep 17 00:00:00 2001 From: Jose Luis Date: Sat, 21 Mar 2026 15:46:28 +0100 Subject: [PATCH] fix: enable touch panning and prevent page scroll on mobile - Add touch-action: none on canvas to prevent browser scroll hijack - Single-finger touch on empty canvas now triggers pan (pointerType check) - Fix page bounce on mobile with position: fixed and 100dvh height Co-Authored-By: Claude Opus 4.6 (1M context) --- src/App.jsx | 9 ++++++++- src/game/PuzzleView.jsx | 9 ++++++++- src/index.css | 6 +++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 3e7ef56..b9d8a28 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -91,10 +91,17 @@ export default function App({ onSwitchToGame }) { state.panStart = { x: e.clientX - state.camX, y: e.clientY - state.camY }; e.preventDefault(); } else if (e.button === 0 && !connectingRef.current) { + // On mobile (touch), single finger on empty canvas = pan + if (isMobile && e.pointerType === 'touch') { + state.panning = true; + state.panStart = { x: e.clientX - state.camX, y: e.clientY - state.camY }; + e.preventDefault(); + return; + } state.selectedModuleId = null; emit(); } - }, []); + }, [isMobile]); const handlePointerMove = useCallback((e) => { if (state.panning && state.panStart) { diff --git a/src/game/PuzzleView.jsx b/src/game/PuzzleView.jsx index 3d5b011..0c32da9 100644 --- a/src/game/PuzzleView.jsx +++ b/src/game/PuzzleView.jsx @@ -131,10 +131,17 @@ export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onN state.panStart = { x: e.clientX - state.camX, y: e.clientY - state.camY }; e.preventDefault(); } else if (e.button === 0 && !connectingRef.current) { + // On mobile (touch), single finger on empty canvas = pan + if (isMobile && e.pointerType === 'touch') { + state.panning = true; + state.panStart = { x: e.clientX - state.camX, y: e.clientY - state.camY }; + e.preventDefault(); + return; + } state.selectedModuleId = null; emit(); } - }, []); + }, [isMobile]); const handlePointerMove = useCallback((e) => { if (state.panning && state.panStart) { diff --git a/src/index.css b/src/index.css index 917e088..0e28381 100644 --- a/src/index.css +++ b/src/index.css @@ -802,6 +802,10 @@ html, body, #root { @media (max-width: 768px) { + /* --- Prevent page scroll/bounce --- */ + html, body, #root { overflow: hidden; position: fixed; width: 100%; height: 100%; } + .app, .gm-puzzle { overflow: hidden; height: 100dvh; } + /* --- Sandbox Toolbar --- */ .toolbar { height: 44px; padding: 0 12px; gap: 6px; } .toolbar-title { font-size: 13px; letter-spacing: 0.8px; } @@ -924,7 +928,7 @@ html, body, #root { } /* --- Canvas adjustments --- */ - .node-canvas { cursor: default; } + .node-canvas { cursor: default; touch-action: none; } .zoom-controls { right: 8px; top: 8px; } .zoom-btn { width: 40px; height: 36px; min-height: 44px; } .port-dot { width: 18px; height: 18px; }