fix mobile camera controls
This commit is contained in:
@@ -190,6 +190,7 @@ mc-diorama {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
touch-action: none;
|
||||||
|
|
||||||
& .track-inner {
|
& .track-inner {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -230,6 +230,111 @@ export class SpacetimeController {
|
|||||||
this.syncUI();
|
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;
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(this.touchTimer);
|
||||||
|
|
||||||
|
// Instantly engage if multi-touch (pinch/pan). No wait required!
|
||||||
|
if (e.touches.length >= 2) {
|
||||||
|
this.touchEngaged = true;
|
||||||
|
this.hasDragged = true;
|
||||||
|
if (e.cancelable) e.preventDefault();
|
||||||
|
} else {
|
||||||
|
// Single touch: wait to see if it's a page scroll
|
||||||
|
this.touchEngaged = false;
|
||||||
|
this.touchTimer = setTimeout(() => {
|
||||||
|
this.touchEngaged = true;
|
||||||
|
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 (1 finger).
|
||||||
|
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 (maxDist > 10) clearTimeout(this.touchTimer);
|
||||||
|
return; // Let the browser scroll the page
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CAMERA IS ENGAGED ---
|
||||||
|
if (e.cancelable) e.preventDefault(); // Block native pinch-zoom and 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.zoomRatio(lastDist / currentDist);
|
||||||
|
|
||||||
|
// 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 ---
|
// --- TEMPORAL CONTROL ---
|
||||||
@@ -273,19 +378,26 @@ export class SpacetimeController {
|
|||||||
onDrag(pct);
|
onDrag(pct);
|
||||||
};
|
};
|
||||||
|
|
||||||
wrapper.addEventListener('mousedown', (e) => {
|
wrapper.addEventListener('pointerdown', (e) => {
|
||||||
|
wrapper.setPointerCapture(e.pointerId);
|
||||||
isTrackDragging = true;
|
isTrackDragging = true;
|
||||||
this.playMode = 'paused';
|
this.playMode = 'paused';
|
||||||
update(e);
|
update(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('mousemove', (e) => {
|
wrapper.addEventListener('pointermove', (e) => {
|
||||||
if (isTrackDragging) update(e);
|
if (isTrackDragging) update(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('mouseup', () => {
|
const stopDrag = (e) => {
|
||||||
|
if (isTrackDragging) {
|
||||||
isTrackDragging = false;
|
isTrackDragging = false;
|
||||||
});
|
wrapper.releasePointerCapture(e.pointerId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
wrapper.addEventListener('pointerup', stopDrag);
|
||||||
|
wrapper.addEventListener('pointercancel', stopDrag);
|
||||||
}
|
}
|
||||||
|
|
||||||
bindTimelineEvents() {
|
bindTimelineEvents() {
|
||||||
|
|||||||
Reference in New Issue
Block a user