From e57b0bc784d71d4a546c8544bc43e0dad6d4c43e Mon Sep 17 00:00:00 2001 From: agent Date: Sun, 19 Jul 2026 04:32:19 +0000 Subject: [PATCH] =?UTF-8?q?v0.6:=20Mobile-Touch-Patches=20f=C3=BCr=20obv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- vault.html | 75 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/vault.html b/vault.html index aca2edf..ea0e421 100644 --- a/vault.html +++ b/vault.html @@ -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);