fix touch actions

This commit is contained in:
David Allemang
2026-07-09 12:44:34 -04:00
parent 02911e0357
commit 35e3312d17
2 changed files with 114 additions and 5 deletions

View File

@@ -190,6 +190,7 @@ mc-diorama {
cursor: pointer;
display: flex;
align-items: center;
touch-action: none;
& .track-inner {
position: relative;

View File

@@ -230,6 +230,107 @@ export class SpacetimeController {
this.syncUI();
}
});
// --- MOBILE TOUCH LOGIC ("Wait and See" Scroll vs Camera) ---
this.touchEngaged = false;
this.touchTimer = null;
this.touchStartPos = new Map();
this.lastTouchPos = new Map();
this.canvas.addEventListener('touchstart', (e) => {
if (!this.hasSpatial) return;
// DO NOT call e.preventDefault() here, or the page will never scroll!
for (let i = 0; i < e.changedTouches.length; i++) {
const t = e.changedTouches[i];
this.touchStartPos.set(t.identifier, {x: t.clientX, y: t.clientY});
this.lastTouchPos.set(t.identifier, {x: t.clientX, y: t.clientY});
}
// A new finger restarts the timeout
clearTimeout(this.touchTimer);
this.touchEngaged = false;
this.touchTimer = setTimeout(() => {
this.touchEngaged = true;
// Once engaged, disable click propagation so it doesn't toggle play/pause
this.hasDragged = true;
}, 200);
}, {passive: false});
this.canvas.addEventListener('touchmove', (e) => {
if (!this.hasSpatial) return;
if (!this.touchEngaged) {
// Check if they are swiping quickly before the timer completes.
let maxDist = 0;
for (let i = 0; i < e.touches.length; i++) {
const t = e.touches[i];
const start = this.touchStartPos.get(t.identifier);
if (start) {
maxDist = Math.max(maxDist, Math.hypot(t.clientX - start.x, t.clientY - start.y));
}
}
// If they drag more than 10px before the 200ms timer, assume page scroll intent
if (maxDist > 10) clearTimeout(this.touchTimer);
return; // Let the browser scroll the page
}
// --- CAMERA IS ENGAGED ---
e.preventDefault(); // Stop the page from scrolling
this.hasDragged = true;
if (e.touches.length === 1) {
// 1 Finger: Orbit
const t = e.touches[0];
const last = this.lastTouchPos.get(t.identifier);
if (last) {
this.orbit(t.clientX - last.x, t.clientY - last.y);
notifyCameraChange();
}
this.lastTouchPos.set(t.identifier, {x: t.clientX, y: t.clientY});
} else if (e.touches.length === 2) {
// 2 Fingers: Pan and Zoom
const t1 = e.touches[0];
const t2 = e.touches[1];
const l1 = this.lastTouchPos.get(t1.identifier);
const l2 = this.lastTouchPos.get(t2.identifier);
if (l1 && l2) {
// Zoom (Pinch distance delta)
const lastDist = Math.hypot(l1.x - l2.x, l1.y - l2.y);
const currentDist = Math.hypot(t1.clientX - t2.clientX, t1.clientY - t2.clientY);
this.zoomLinear((lastDist - currentDist) * 1.5); // inverted for pinch-to-zoom
// Pan (Center point delta)
const lastCx = (l1.x + l2.x) / 2;
const lastCy = (l1.y + l2.y) / 2;
const cx = (t1.clientX + t2.clientX) / 2;
const cy = (t1.clientY + t2.clientY) / 2;
this.pan(cx - lastCx, cy - lastCy);
notifyCameraChange();
}
this.lastTouchPos.set(t1.identifier, {x: t1.clientX, y: t1.clientY});
this.lastTouchPos.set(t2.identifier, {x: t2.clientX, y: t2.clientY});
}
}, {passive: false});
const handleTouchEnd = (e) => {
for (let i = 0; i < e.changedTouches.length; i++) {
const t = e.changedTouches[i];
this.touchStartPos.delete(t.identifier);
this.lastTouchPos.delete(t.identifier);
}
if (e.touches.length === 0) {
clearTimeout(this.touchTimer);
this.touchEngaged = false;
}
};
this.canvas.addEventListener('touchend', handleTouchEnd);
this.canvas.addEventListener('touchcancel', handleTouchEnd);
}
// --- TEMPORAL CONTROL ---
@@ -273,19 +374,26 @@ export class SpacetimeController {
onDrag(pct);
};
wrapper.addEventListener('mousedown', (e) => {
wrapper.addEventListener('pointerdown', (e) => {
wrapper.setPointerCapture(e.pointerId);
isTrackDragging = true;
this.playMode = 'paused';
update(e);
});
window.addEventListener('mousemove', (e) => {
wrapper.addEventListener('pointermove', (e) => {
if (isTrackDragging) update(e);
});
window.addEventListener('mouseup', () => {
isTrackDragging = false;
});
const stopDrag = (e) => {
if (isTrackDragging) {
isTrackDragging = false;
wrapper.releasePointerCapture(e.pointerId);
}
};
wrapper.addEventListener('pointerup', stopDrag);
wrapper.addEventListener('pointercancel', stopDrag);
}
bindTimelineEvents() {