feat(expert-mode): improve mobile touch controls for 3D scene

- Simplified OrbitControls touch gesture configuration:
  - ONE: THREE.TOUCH.ROTATE (one-finger orbit)
  - TWO: THREE.TOUCH.DOLLY (pinch zoom only, no accidental pan)
  - THREE: THREE.TOUCH.PAN (three-finger pan)

- Removed complex dynamic enablePan toggling that didn't work reliably
  with OrbitControls' internal touch processing

- Added iOS Safari-specific touch improvements:
  - Double-tap zoom prevention
  - Touch action and user select CSS properties
  - -webkit-tap-highlight-color: transparent

- Enhanced CSS for better mobile touch handling:
  - touch-action: none on scene container and canvas
  - -webkit-touch-callout: none
  - -webkit-user-select: none
  - user-select: none

All mobile tests (29 tests) and quick-actions tests (22 tests) pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-25 11:00:56 -04:00
parent 7aae1c2a46
commit ef9cd3fe15
13 changed files with 75 additions and 67 deletions

File diff suppressed because one or more lines are too long

View file

@ -11,6 +11,6 @@
"cost_usd": null,
"captured_at": "2026-04-11T00:53:39.772264711Z",
"trace_format": "claude_json",
"pruned": false,
"pruned": true,
"template_version": null
}

View file

@ -1,3 +0,0 @@
```json
{"splittable": false}
```

View file

@ -11,6 +11,6 @@
"cost_usd": null,
"captured_at": "2026-04-11T00:53:51.503031372Z",
"trace_format": "claude_json",
"pruned": false,
"pruned": true,
"template_version": null
}

View file

@ -1,3 +0,0 @@
```json
{"splittable": false}
```

View file

@ -1 +1 @@
03fd4e2752b6afb09385b74d87a7c1b7ff9061aa
ab5e77352d2cf097851d9b0e8b9a8edf2d8e5605

View file

@ -261,6 +261,11 @@ body {
width: 100%;
height: 100%;
touch-action: none;
/* Mobile-specific touch handling improvements */
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
/* Live-view overlay panels (float over canvas)

View file

@ -227,6 +227,12 @@
height: 100%;
touch-action: none;
z-index: 1;
/* Mobile-specific touch handling improvements */
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
/* Prevent iOS Safari from delaying single taps */
-webkit-tap-highlight-color: transparent;
}
/* iOS Safari-specific handling for visual viewport */
@ -251,6 +257,11 @@
max-width: 100%;
max-height: 100%;
touch-action: none;
/* Mobile-specific touch handling improvements */
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
/* Handle visual viewport resize events (iOS Safari keyboard, address bar) */

View file

@ -188,58 +188,52 @@
// Touch gesture configuration for OrbitControls
// One finger: rotate (orbit)
// Two fingers: pinch-to-zoom (dolly) only
// Two fingers: pinch-to-zoom (dolly) only - NO pan on two fingers
// Three fingers: pan (native OrbitControls support)
//
// Using OrbitControls' built-in touch configuration:
// - ONE: THREE.TOUCH.ROTATE (one-finger orbit)
// - TWO: THREE.TOUCH.DOLLY_PAN (pinch zoom + two-finger pan, with pan disabled below)
// - TWO: THREE.TOUCH.DOLLY (pinch zoom ONLY, no pan)
// - THREE: THREE.TOUCH.PAN (three-finger pan)
//
// Note: We set enablePan=false initially to prevent two-finger pan,
// allowing only pinch zoom on two fingers. Three-finger pan works
// through the THREE.TOUCH.PAN configuration.
// Note: THREE.TOUCH.DOLLY (not DOLLY_PAN) ensures two-finger touch
// only performs pinch zoom, eliminating accidental pan during zoom.
controls.touches = {
ONE: THREE.TOUCH.ROTATE, // One-finger touch rotates the camera
TWO: THREE.TOUCH.DOLLY_PAN, // Two-finger touch zooms (pinch) and pans
TWO: THREE.TOUCH.DOLLY, // Two-finger touch zooms (pinch) ONLY - no pan
THREE: THREE.TOUCH.PAN // Three-finger touch pans the camera
};
// Disable two-finger pan to prevent accidental pan during pinch zoom
// Only allow three-finger pan for deliberate camera panning
controls.listenToKeyEvents(window); // Enable keyboard controls for desktop
// Custom touch event handling to prevent two-finger pan while allowing three-finger pan
const element = renderer.domElement;
let originalEnablePan = controls.enablePan;
// Mobile-specific touch handling improvements
// Prevent default browser behaviors that interfere with OrbitControls
const canvasElement = renderer.domElement;
element.addEventListener('touchstart', function(event) {
// Disable pan for two-finger touch to prevent accidental pan during pinch zoom
if (event.touches.length === 2) {
controls.enablePan = false;
} else if (event.touches.length === 3) {
// Enable pan for three-finger touch
controls.enablePan = true;
} else {
controls.enablePan = originalEnablePan;
// Ensure touch events are properly handled on mobile devices
canvasElement.style.touchAction = 'none';
canvasElement.style.webkitTouchCallout = 'none';
canvasElement.style.webkitUserSelect = 'none';
canvasElement.style.userSelect = 'none';
// Handle iOS Safari-specific touch issues
// Prevent double-tap zoom on iOS
canvasElement.addEventListener('touchstart', function(event) {
if (event.touches.length === 1) {
// Store timestamp for double-tap detection
canvasElement._lastTouchStart = Date.now();
}
}, { passive: true });
element.addEventListener('touchend', function(event) {
// Restore pan state when fingers are lifted
if (event.touches.length === 0) {
controls.enablePan = originalEnablePan;
} else if (event.touches.length === 2) {
controls.enablePan = false;
} else if (event.touches.length === 3) {
controls.enablePan = true;
canvasElement.addEventListener('touchend', function(event) {
if (event.touches.length === 0 && canvasElement._lastTouchStart) {
const timeSinceLastTouch = Date.now() - canvasElement._lastTouchStart;
// If it's a quick tap (potential double-tap), prevent default
if (timeSinceLastTouch < 300) {
event.preventDefault();
}
}
}, { passive: true });
element.addEventListener('touchcancel', function() {
// Restore pan state on touch cancel
controls.enablePan = originalEnablePan;
}, { passive: true });
}, { passive: false });
// Grid helper (XZ plane, Y-up)
gridHelper = new THREE.GridHelper(

Binary file not shown.

Binary file not shown.