commit b571fe5a75806641b7d1cf68381bc60bd759d031
parent 115d620a134bd99809ddf2811cee0275f4ca6f8c
Author: Hunter
Date: Wed, 22 Oct 2025 16:54:37 -0400
address iOS/Safari touch event bug (occurs when zoomed in quickly tapping grid, then image)
Diffstat:
| M | script.js | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- |
1 file changed, 73 insertions(+), 8 deletions(-)
diff --git a/script.js b/script.js
@@ -353,7 +353,7 @@ function setupImageHandlers(imageData) {
});
// Unified pointer start handler
- function handlePointerStart(clientX, clientY, isTouch = false) {
+ function handlePointerStart(clientX, clientY, isTouch = false, touchIdentifier = null) {
// Clear any existing timers from other images
clearDragState();
@@ -368,17 +368,27 @@ function setupImageHandlers(imageData) {
startXCell: imageData.xCell,
startYCell: imageData.yCell,
isPanMode: false, // Will be set to true after long press
- isTouch: isTouch
+ isTouch: isTouch,
+ timerId: null, // Store timer ID to ensure we only activate the correct timer
+ touchIdentifier: touchIdentifier, // Track which touch this drag belongs to
+ hasMoved: false // Track if any movement has occurred
};
container.classList.add('dragging');
// For touch, set up long press timer to enable pan mode
if (isTouch) {
- longPressTimer = setTimeout(() => {
- if (dragState && dragState.image === imageData &&
+ const timerId = setTimeout(() => {
+ // Only activate pan mode if:
+ // 1. This drag state is still active
+ // 2. This drag state is for this specific image
+ // 3. The image hasn't moved to a new cell
+ // 4. This is the timer that was created for this drag state
+ if (dragState &&
+ dragState.image === imageData &&
dragState.startXCell === imageData.xCell &&
- dragState.startYCell === imageData.yCell) {
+ dragState.startYCell === imageData.yCell &&
+ dragState.timerId === timerId) {
// User held for 0.5 seconds without moving to a new cell - enable pan mode
dragState.isPanMode = true;
dragState.initialPanX = imageData.panX;
@@ -387,6 +397,8 @@ function setupImageHandlers(imageData) {
container.classList.add('pan-mode');
}
}, 500);
+ dragState.timerId = timerId;
+ longPressTimer = timerId;
}
}
@@ -401,7 +413,7 @@ function setupImageHandlers(imageData) {
// Single touch - start drag (or long press for pan)
e.preventDefault();
const touch = e.touches[0];
- handlePointerStart(touch.clientX, touch.clientY, true);
+ handlePointerStart(touch.clientX, touch.clientY, true, touch.identifier);
} else if (e.touches.length === 2) {
// Two fingers - prepare for pinch/pan
e.preventDefault();
@@ -712,6 +724,24 @@ function handleMove(clientX, clientY) {
const dx = clientX - dragState.startX;
const dy = clientY - dragState.startY;
+ // Safari on iOS can send the first touchmove with stale coordinates when zoomed
+ // Validate that the first move is reasonable by checking if it would move more than 1 cell
+ if (dragState.isTouch && !dragState.hasMoved) {
+ const cellSize = getCellSize();
+ const dxCells = Math.abs(Math.round(dx / cellSize.width));
+ const dyCells = Math.abs(Math.round(dy / cellSize.height));
+
+ // If the first move would jump more than 1 cell in either direction,
+ // it's likely stale coordinates from a previous tap - reset start position
+ if (dxCells > 1 || dyCells > 1) {
+ dragState.startX = clientX;
+ dragState.startY = clientY;
+ dragState.hasMoved = true;
+ return; // Don't process this move event
+ }
+ dragState.hasMoved = true;
+ }
+
if (dragState.isPanMode) {
// Pan mode - move the image within its container
const imageData = dragState.image;
@@ -814,18 +844,53 @@ document.addEventListener('mouseup', () => {
document.addEventListener('touchmove', (e) => {
// Only handle global drag/resize, not image-specific multi-touch
if ((dragState || resizeState) && e.touches.length === 1) {
- e.preventDefault();
const touch = e.touches[0];
+
+ // For drag operations, verify this touch matches the one that started the drag
+ if (dragState && dragState.touchIdentifier !== null &&
+ touch.identifier !== dragState.touchIdentifier) {
+ // This is a different touch - ignore it
+ return;
+ }
+
+ e.preventDefault();
handleMove(touch.clientX, touch.clientY);
}
}, { passive: false });
-document.addEventListener('touchend', () => {
+document.addEventListener('touchend', (e) => {
+ // If we have an active drag state, only end it if the touch that's ending
+ // matches the touch that started the drag
+ if (dragState && dragState.touchIdentifier !== null && e.changedTouches.length > 0) {
+ let matchingTouchEnded = false;
+ for (let i = 0; i < e.changedTouches.length; i++) {
+ if (e.changedTouches[i].identifier === dragState.touchIdentifier) {
+ matchingTouchEnded = true;
+ break;
+ }
+ }
+ // Only end the drag if the matching touch ended
+ if (!matchingTouchEnded) {
+ return;
+ }
+ }
+
handleEnd();
+
+ // Clear any lingering timers even if there's no active drag state
+ if (longPressTimer) {
+ clearTimeout(longPressTimer);
+ longPressTimer = null;
+ }
});
document.addEventListener('touchcancel', () => {
handleEnd();
+
+ if (longPressTimer) {
+ clearTimeout(longPressTimer);
+ longPressTimer = null;
+ }
});
// Update all image positions when window resizes (for responsive scaling)