v0.6: Mobile-Touch-Patches für obv
Drei Probleme, drei Fixes:
1. Touch-Drag auf Graph geht nicht:
obv's mousedown/mousemove/mouseup sind Mausevents only.
Fix: zusätzlich touchstart/touchmove/touchend/touchcancel
+ getPos() Helper der touches[0] oder clientX nutzt
+ preventDefault() auf Drag (sonst versucht iOS zu scrollen)
2. Suche und Content-Scroll blockiert:
obv's animateGraph() läuft mit requestAnimationFrame endlos
und blockt den Main-Thread auf iOS. Plus iOS-300ms-Tap-Delay
auf inline onclick=.
Fix:
- document.hidden check (skip rendering wenn Tab im Hintergrund)
- autoRotate pausiert 3s nach User-Interaktion
- html, body { touch-action: manipulation } (entfernt 300ms delay)
- -webkit-tap-highlight-color: transparent (entfernt blauen Flash)
- .vault-content, .vault-sidebar { touch-action: pan-y, -webkit-overflow-scrolling: touch }
- .vault-graph canvas { touch-action: none } (damit Browser-Default Three.js-Drag nicht blockt)
Nicht angefasst: obv's Inline-onclick-Handler (Suche-Eingabe, File-Click,
WikiLinks) — diese funktionieren sobald der 300ms-Tap-Delay weg ist
und der Main-Thread nicht mehr blockt.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+60
-15
@@ -13,7 +13,8 @@
|
||||
--accent: #FFD700; --link: #FFD700; --border: #313244;
|
||||
--green: #a6e3a1; --red: #f38ba8; --blue: #89b4fa; --teal: #94e2d5;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
||||
html, body { touch-action: manipulation; }
|
||||
body { background: var(--bg-primary); color: var(--text-primary); font-family: -apple-system, 'Segoe UI', Inter, sans-serif; height: 100vh; overflow: hidden; }
|
||||
|
||||
.vault-app {
|
||||
@@ -47,7 +48,8 @@ body { background: var(--bg-primary); color: var(--text-primary); font-family: -
|
||||
/* Sidebar file tree */
|
||||
.vault-sidebar {
|
||||
background: var(--bg-secondary); border-right: 1px solid var(--border);
|
||||
overflow-y: auto; padding: 8px 0; font-size: 13px;
|
||||
overflow-y: auto; -webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-y; padding: 8px 0; font-size: 13px;
|
||||
}
|
||||
.tree-folder { cursor: pointer; user-select: none; }
|
||||
.tree-folder-header {
|
||||
@@ -68,7 +70,8 @@ body { background: var(--bg-primary); color: var(--text-primary); font-family: -
|
||||
|
||||
/* Content area */
|
||||
.vault-content {
|
||||
overflow-y: auto; padding: 32px 48px; max-width: 900px;
|
||||
overflow-y: auto; -webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-y; padding: 32px 48px; max-width: 900px;
|
||||
line-height: 1.7; font-size: 15px;
|
||||
}
|
||||
.vault-content h1 { color: var(--accent); font-size: 28px; margin: 24px 0 12px; }
|
||||
@@ -94,9 +97,9 @@ body { background: var(--bg-primary); color: var(--text-primary); font-family: -
|
||||
/* Graph panel */
|
||||
.vault-graph {
|
||||
background: var(--bg-secondary); border-left: 1px solid var(--border);
|
||||
position: relative; overflow: hidden;
|
||||
position: relative; overflow: hidden; touch-action: none;
|
||||
}
|
||||
.vault-graph canvas { width: 100% !important; height: 100% !important; }
|
||||
.vault-graph canvas { width: 100% !important; height: 100% !important; touch-action: none; }
|
||||
|
||||
/* Search results overlay */
|
||||
#search-results {
|
||||
@@ -321,23 +324,65 @@ async function loadGraph() {
|
||||
graphScene.add(new THREE.Line(geo, mat));
|
||||
});
|
||||
|
||||
// Orbit
|
||||
// Orbit (mouse + touch support)
|
||||
let isDrag = false, lastX = 0, lastY = 0, rotX = 0, rotY = 0;
|
||||
canvas.addEventListener('mousedown', e => { isDrag = true; lastX = e.clientX; lastY = e.clientY; });
|
||||
canvas.addEventListener('mousemove', e => {
|
||||
function getPos(e) {
|
||||
if (e.touches && e.touches.length) {
|
||||
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
||||
}
|
||||
return { x: e.clientX, y: e.clientY };
|
||||
}
|
||||
function onDragStart(e) {
|
||||
e.preventDefault();
|
||||
isDrag = true;
|
||||
const p = getPos(e);
|
||||
lastX = p.x; lastY = p.y;
|
||||
}
|
||||
function onDragMove(e) {
|
||||
if (!isDrag) return;
|
||||
rotY += (e.clientX - lastX) * 0.005;
|
||||
rotX += (e.clientY - lastY) * 0.005;
|
||||
lastX = e.clientX; lastY = e.clientY;
|
||||
});
|
||||
canvas.addEventListener('mouseup', () => isDrag = false);
|
||||
canvas.addEventListener('wheel', e => {
|
||||
e.preventDefault();
|
||||
const p = getPos(e);
|
||||
rotY += (p.x - lastX) * 0.005;
|
||||
rotX += (p.y - lastY) * 0.005;
|
||||
lastX = p.x; lastY = p.y;
|
||||
}
|
||||
function onDragEnd(e) {
|
||||
if (e && e.preventDefault) e.preventDefault();
|
||||
isDrag = false;
|
||||
}
|
||||
function onWheel(e) {
|
||||
graphCamera.position.z += e.deltaY * 0.5;
|
||||
graphCamera.position.z = Math.max(50, Math.min(800, graphCamera.position.z));
|
||||
});
|
||||
}
|
||||
canvas.addEventListener('mousedown', onDragStart);
|
||||
canvas.addEventListener('mousemove', onDragMove);
|
||||
window.addEventListener('mouseup', onDragEnd);
|
||||
canvas.addEventListener('touchstart', onDragStart, { passive: false });
|
||||
canvas.addEventListener('touchmove', onDragMove, { passive: false });
|
||||
canvas.addEventListener('touchend', onDragEnd, { passive: false });
|
||||
canvas.addEventListener('touchcancel', onDragEnd, { passive: false });
|
||||
canvas.addEventListener('wheel', onWheel, { passive: true });
|
||||
|
||||
// Auto-rotate: when user is not interacting, slow rotation.
|
||||
// Pauses when tab is hidden, when user is dragging, and after page visibility loss.
|
||||
let autoRotate = true;
|
||||
let lastInteractionTime = 0;
|
||||
const AUTO_ROTATE_RESUME_MS = 3000; // resume auto-rotation 3s after user stops dragging
|
||||
|
||||
function animateGraph() {
|
||||
requestAnimationFrame(animateGraph);
|
||||
if (document.hidden) return; // skip rendering when tab is in background
|
||||
const now = Date.now();
|
||||
const userJustInteracted = (now - lastInteractionTime) < AUTO_ROTATE_RESUME_MS;
|
||||
if (isDrag) {
|
||||
lastInteractionTime = now;
|
||||
autoRotate = false;
|
||||
} else if (!userJustInteracted) {
|
||||
autoRotate = true;
|
||||
}
|
||||
if (autoRotate && !isDrag) {
|
||||
rotY += 0.002;
|
||||
}
|
||||
graphScene.rotation.y = rotY;
|
||||
graphScene.rotation.x = rotX;
|
||||
graphRenderer.render(graphScene, graphCamera);
|
||||
|
||||
Reference in New Issue
Block a user