diff --git a/README.md b/README.md index 0171062..cc6dffa 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,20 @@ aus dem Browser einmalig hineingeschrieben. Im Bearbeiten-Modus lassen sich Elemente ziehen, mit den Pfeiltasten pixelweise schieben (`⇧` = 10 px) und über `A−`/`A+` in der Größe ändern. +### Auf der Fläche bewegen + +| Maus | Wirkung | +|---|---| +| Ziehen | Ausschnitt schieben | +| Rad | Zoomen, auf den Zeiger zu | +| `⇧` + Rad | Waagerecht schieben | +| Leertaste-Ziehen, mittlere Taste | Schieben, auch im Bearbeiten-Modus | + +Ein gewöhnliches Mausrad genügt, ohne Zusatztaste. Im Bearbeiten-Modus zieht +die linke Taste das Element unter dem Zeiger — die Fläche schiebt man dort mit +gedrückter Leertaste oder der mittleren Taste. `Fit` unten rechts setzt den +Maßstab zurück. + ## Aufbau ``` diff --git a/post1-alt.html b/post1-alt.html index 7198bf5..fe9c39c 100644 --- a/post1-alt.html +++ b/post1-alt.html @@ -1161,14 +1161,22 @@ function setZoom(k, anchor) { const scroller = document.getElementById("scroller"); const before = zoom || autoFit(); - zoom = Math.min(2, Math.max(0.05, k)); - // Um den Mauszeiger zoomen, nicht um die linke obere Ecke. - const ratio = zoom / before; const ax = anchor ? anchor.x : scroller.clientWidth / 2; const ay = anchor ? anchor.y : scroller.clientHeight / 2; + /* Um den Mauszeiger zoomen, nicht um die linke obere Ecke — und dafür den + Punkt unter dem Zeiger messen statt ihn aus scrollLeft hochzurechnen. + Das Blatt sitzt in einer gepolsterten Fläche und wird zentriert, solange + Platz übrig ist; beides verschiebt den Ursprung. Ein reiner Dreisatz + ließ den Inhalt deshalb bei jedem Schritt um das Polster wegwandern. */ + const s0 = scroller.getBoundingClientRect(); + const r0 = deck.getBoundingClientRect(); + const u = (s0.left + ax - r0.left) / before; + const v = (s0.top + ay - r0.top) / before; + zoom = Math.min(2, Math.max(0.05, k)); fitStrip(); - scroller.scrollLeft = (scroller.scrollLeft + ax) * ratio - ax; - scroller.scrollTop = (scroller.scrollTop + ay) * ratio - ay; + const r1 = deck.getBoundingClientRect(); + scroller.scrollLeft += (r1.left + u * zoom) - (s0.left + ax); + scroller.scrollTop += (r1.top + v * zoom) - (s0.top + ay); } const setStatus = (msg, kind) => { @@ -1880,7 +1888,12 @@ show(Number(holder.dataset.n)); pick(idx, key); - const k = deck.getBoundingClientRect().width / (1080 * (view === "streifen" ? SLIDES.length : 1)); + // Den Maßstab nehmen, mit dem fitStrip gerechnet hat, nicht das Rechteck + // messen: .strip ist ein Flex-Kind der schon skalierten Hülle, seine + // Border-Box misst deshalb Breite mal Maßstab hoch zwei. Geteilt wurde + // damit durch das Quadrat — bei eingepasster Ansicht wanderte das Element + // um ein Vielfaches der Mausbewegung. + const k = zoom || autoFit(); const set = (edits[idx][key] = edits[idx][key] || {}); const startX = e.clientX, startY = e.clientY; const baseX = set.dx || 0, baseY = set.dy || 0; @@ -2031,21 +2044,40 @@ }); window.addEventListener("resize", fitStrip); - /* Strg/Cmd + Rad zoomt, blankes Rad scrollt. Trackpad-Pinch schickt - ctrlKey mit, verhält sich also automatisch richtig. */ + /* deltaMode: 0 = Pixel, 1 = Zeilen (Firefox), 2 = Seiten. Ohne Umrechnung + zoomt Firefox pro Rastung um ein Vielfaches weniger als Chrome. */ + function wheelPx(v, mode) { return v * (mode === 1 ? 16 : mode === 2 ? 400 : 1); } + + /* Blankes Rad zoomt — eine gewöhnliche PC-Maus soll ohne Zusatztaste + auskommen. Strg/Cmd + Rad tut dasselbe, damit der Trackpad-Pinch (der + ctrlKey mitschickt) weiter greift. Waagerechtes Wischen und ⇧ + Rad + schieben dagegen den Ausschnitt: ein Trackpad darf sich nicht wie eine + Zoomwippe anfühlen. */ document.getElementById("scroller").addEventListener("wheel", e => { - if (!e.ctrlKey && !e.metaKey) return; + const sc = e.currentTarget; + const dx = wheelPx(e.deltaX, e.deltaMode), dy = wheelPx(e.deltaY, e.deltaMode); + if (e.shiftKey || (!dy && dx)) { + e.preventDefault(); + sc.scrollLeft += (dx || dy); + return; + } + if (!dy) return; e.preventDefault(); - const r = e.currentTarget.getBoundingClientRect(); - setZoom((zoom || autoFit()) * (e.deltaY < 0 ? 1.12 : 1 / 1.12), + const r = sc.getBoundingClientRect(); + // Exponentiell gerechnet fühlen sich Rastung (~100 px auf einen Schlag) + // und Trackpad (viele kleine Werte) gleich an. Der Deckel hält die + // Schwung-Schübe im Zaum, die macOS nach dem Loslassen nachschiebt. + const k = Math.min(3, Math.max(1 / 3, Math.exp(-dy * 0.0015))); + setZoom((zoom || autoFit()) * k, { x: e.clientX - r.left, y: e.clientY - r.top }); }, { passive: false }); - /* Panning als verlässlicher Weg, unabhängig von Trackpad-Gesten: - Leertaste gedrückt halten und ziehen, oder mittlere Maustaste. */ + /* Panning: linke Maustaste auf der Fläche reicht. Leertaste und mittlere + Taste bleiben als gewohnte Wege bestehen — im Bearbeiten-Modus sind sie + der einzige, denn dort gehört der linke Zug dem Element unter dem Zeiger. */ (function panning() { const sc = document.getElementById("scroller"); - let space = false, active = null; + let space = false, active = null, dragged = false; document.addEventListener("keydown", e => { if (e.code !== "Space" || e.target.matches("input, textarea")) return; @@ -2058,19 +2090,37 @@ }); sc.addEventListener("mousedown", e => { - if (!space && e.button !== 1) return; + dragged = false; + if (!space && e.button !== 1) { + // Linke Taste zieht die Fläche, außer sie hat im Bearbeiten-Modus + // ein Element erwischt: das verschiebt sich selbst. + if (e.button !== 0) return; + if (editMode === "an" && e.target.closest("[data-edit]")) return; + } e.preventDefault(); active = { x: e.clientX, y: e.clientY, l: sc.scrollLeft, t: sc.scrollTop }; sc.classList.add("panning"); }); document.addEventListener("mousemove", e => { if (!active) return; - sc.scrollLeft = active.l - (e.clientX - active.x); - sc.scrollTop = active.t - (e.clientY - active.y); + const dx = e.clientX - active.x, dy = e.clientY - active.y; + // Erst ab ein paar Pixeln ist es ein Zug und kein Klick, sonst + // verschluckt jede zittrige Hand die Slide-Auswahl. + if (Math.abs(dx) + Math.abs(dy) > 3) dragged = true; + sc.scrollLeft = active.l - dx; + sc.scrollTop = active.t - dy; }); document.addEventListener("mouseup", () => { active = null; sc.classList.remove("panning"); }); + // Nach echtem Ziehen den Klick schlucken: sonst springt die Auswahl auf + // die Slide, über der die Maus zufällig loslässt. + sc.addEventListener("click", e => { + if (!dragged) return; + dragged = false; + e.stopPropagation(); + e.preventDefault(); + }, true); })(); document.getElementById("zoom-in").addEventListener("click", () => setZoom((zoom || autoFit()) * 1.25)); diff --git a/post1.html b/post1.html index c5b44d3..1f3fbb8 100644 --- a/post1.html +++ b/post1.html @@ -1208,14 +1208,22 @@ function setZoom(k, anchor) { const scroller = document.getElementById("scroller"); const before = zoom || autoFit(); - zoom = Math.min(2, Math.max(0.05, k)); - // Um den Mauszeiger zoomen, nicht um die linke obere Ecke. - const ratio = zoom / before; const ax = anchor ? anchor.x : scroller.clientWidth / 2; const ay = anchor ? anchor.y : scroller.clientHeight / 2; + /* Um den Mauszeiger zoomen, nicht um die linke obere Ecke — und dafür den + Punkt unter dem Zeiger messen statt ihn aus scrollLeft hochzurechnen. + Das Blatt sitzt in einer gepolsterten Fläche und wird zentriert, solange + Platz übrig ist; beides verschiebt den Ursprung. Ein reiner Dreisatz + ließ den Inhalt deshalb bei jedem Schritt um das Polster wegwandern. */ + const s0 = scroller.getBoundingClientRect(); + const r0 = deck.getBoundingClientRect(); + const u = (s0.left + ax - r0.left) / before; + const v = (s0.top + ay - r0.top) / before; + zoom = Math.min(2, Math.max(0.05, k)); fitStrip(); - scroller.scrollLeft = (scroller.scrollLeft + ax) * ratio - ax; - scroller.scrollTop = (scroller.scrollTop + ay) * ratio - ay; + const r1 = deck.getBoundingClientRect(); + scroller.scrollLeft += (r1.left + u * zoom) - (s0.left + ax); + scroller.scrollTop += (r1.top + v * zoom) - (s0.top + ay); } const setStatus = (msg, kind) => { @@ -1870,7 +1878,12 @@ show(Number(holder.dataset.n)); pick(idx, key); - const k = deck.getBoundingClientRect().width / (1080 * (view === "streifen" ? SLIDES.length : 1)); + // Den Maßstab nehmen, mit dem fitStrip gerechnet hat, nicht das Rechteck + // messen: .strip ist ein Flex-Kind der schon skalierten Hülle, seine + // Border-Box misst deshalb Breite mal Maßstab hoch zwei. Geteilt wurde + // damit durch das Quadrat — bei eingepasster Ansicht wanderte das Element + // um ein Vielfaches der Mausbewegung. + const k = zoom || autoFit(); const set = (edits[idx][key] = edits[idx][key] || {}); const startX = e.clientX, startY = e.clientY; const baseX = set.dx || 0, baseY = set.dy || 0; @@ -2021,21 +2034,40 @@ }); window.addEventListener("resize", fitStrip); - /* Strg/Cmd + Rad zoomt, blankes Rad scrollt. Trackpad-Pinch schickt - ctrlKey mit, verhält sich also automatisch richtig. */ + /* deltaMode: 0 = Pixel, 1 = Zeilen (Firefox), 2 = Seiten. Ohne Umrechnung + zoomt Firefox pro Rastung um ein Vielfaches weniger als Chrome. */ + function wheelPx(v, mode) { return v * (mode === 1 ? 16 : mode === 2 ? 400 : 1); } + + /* Blankes Rad zoomt — eine gewöhnliche PC-Maus soll ohne Zusatztaste + auskommen. Strg/Cmd + Rad tut dasselbe, damit der Trackpad-Pinch (der + ctrlKey mitschickt) weiter greift. Waagerechtes Wischen und ⇧ + Rad + schieben dagegen den Ausschnitt: ein Trackpad darf sich nicht wie eine + Zoomwippe anfühlen. */ document.getElementById("scroller").addEventListener("wheel", e => { - if (!e.ctrlKey && !e.metaKey) return; + const sc = e.currentTarget; + const dx = wheelPx(e.deltaX, e.deltaMode), dy = wheelPx(e.deltaY, e.deltaMode); + if (e.shiftKey || (!dy && dx)) { + e.preventDefault(); + sc.scrollLeft += (dx || dy); + return; + } + if (!dy) return; e.preventDefault(); - const r = e.currentTarget.getBoundingClientRect(); - setZoom((zoom || autoFit()) * (e.deltaY < 0 ? 1.12 : 1 / 1.12), + const r = sc.getBoundingClientRect(); + // Exponentiell gerechnet fühlen sich Rastung (~100 px auf einen Schlag) + // und Trackpad (viele kleine Werte) gleich an. Der Deckel hält die + // Schwung-Schübe im Zaum, die macOS nach dem Loslassen nachschiebt. + const k = Math.min(3, Math.max(1 / 3, Math.exp(-dy * 0.0015))); + setZoom((zoom || autoFit()) * k, { x: e.clientX - r.left, y: e.clientY - r.top }); }, { passive: false }); - /* Panning als verlässlicher Weg, unabhängig von Trackpad-Gesten: - Leertaste gedrückt halten und ziehen, oder mittlere Maustaste. */ + /* Panning: linke Maustaste auf der Fläche reicht. Leertaste und mittlere + Taste bleiben als gewohnte Wege bestehen — im Bearbeiten-Modus sind sie + der einzige, denn dort gehört der linke Zug dem Element unter dem Zeiger. */ (function panning() { const sc = document.getElementById("scroller"); - let space = false, active = null; + let space = false, active = null, dragged = false; document.addEventListener("keydown", e => { if (e.code !== "Space" || e.target.matches("input, textarea")) return; @@ -2048,19 +2080,37 @@ }); sc.addEventListener("mousedown", e => { - if (!space && e.button !== 1) return; + dragged = false; + if (!space && e.button !== 1) { + // Linke Taste zieht die Fläche, außer sie hat im Bearbeiten-Modus + // ein Element erwischt: das verschiebt sich selbst. + if (e.button !== 0) return; + if (editMode === "an" && e.target.closest("[data-edit]")) return; + } e.preventDefault(); active = { x: e.clientX, y: e.clientY, l: sc.scrollLeft, t: sc.scrollTop }; sc.classList.add("panning"); }); document.addEventListener("mousemove", e => { if (!active) return; - sc.scrollLeft = active.l - (e.clientX - active.x); - sc.scrollTop = active.t - (e.clientY - active.y); + const dx = e.clientX - active.x, dy = e.clientY - active.y; + // Erst ab ein paar Pixeln ist es ein Zug und kein Klick, sonst + // verschluckt jede zittrige Hand die Slide-Auswahl. + if (Math.abs(dx) + Math.abs(dy) > 3) dragged = true; + sc.scrollLeft = active.l - dx; + sc.scrollTop = active.t - dy; }); document.addEventListener("mouseup", () => { active = null; sc.classList.remove("panning"); }); + // Nach echtem Ziehen den Klick schlucken: sonst springt die Auswahl auf + // die Slide, über der die Maus zufällig loslässt. + sc.addEventListener("click", e => { + if (!dragged) return; + dragged = false; + e.stopPropagation(); + e.preventDefault(); + }, true); })(); document.getElementById("zoom-in").addEventListener("click", () => setZoom((zoom || autoFit()) * 1.25));