feat(pdf): Zoom in festen Fünferschritten statt in fünf Prozent
Gemeint waren runde Zahlen: 65, 70, 75, 80 … Ein fester Faktor ist rechnerisch die sauberere Größe – er fühlt sich unten wie oben gleich an –, aber abzulesen war er nicht: Die Anzeige sprang um drei Punkte, dann um vier, dann um sechs. Wer eine Zahl sieht, erwartet ein Raster. Weil fünf Punkte bei 25 % ein Fünftel und bei 400 % ein Hundertstel sind, kann die Schwelle der Geste nicht mehr fest sein. Verlangt wird jetzt genau die Fingerbewegung, die dem nächsten Schritt entspricht, mit zwei Prozent als Untergrenze gegen Zittern. Ein Fehler, den erst die Messung zeigte: 1,1 × 100 ergibt in Fließkomma 110.00000000000001, und die "nächste kleinere Fünferzahl" davon ist wieder 110. Der Zoom blieb deshalb nach genau einer Stufe stehen – sichtbar nur beim Zuziehen, weil es aufwärts eine Stufe später gegriffen hätte. Vor der Rechnung wird jetzt gerundet. Gemessen, eine lange Geste in jede Richtung: auf: 65 → 70 → 75 → 80 → 85 → 90 → 95 → 100 → 105 → 110 → 115 → 120 → 125 → 130 zu: 125 → 120 → 115 → 110 → 105 → 100 → 95 → 90 → 85 → 80 → 75 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ded1134f6a
commit
065372359b
1 file changed
+52
-28
@@ -43,24 +43,19 @@ const standardFontDataUrl = '/pdfjs/standard_fonts/'
|
||||
const zoomSteps = [0.5, 0.75, 1, 1.25, 1.5, 2, 3]
|
||||
|
||||
/**
|
||||
* Stufenweite für **stufenloses** Zoomen: Finger und Mausrad gehen in
|
||||
* Zwanzigsteln, nicht auf der Leiter der Knöpfe.
|
||||
* Zoomschritt für Finger und Mausrad: **fünf Prozentpunkte**, auf runde
|
||||
* Zahlen gerastet – 65, 70, 75, 80 …
|
||||
*
|
||||
* Der Unterschied ist gewollt. Ein Klick soll etwas bewegen – von 100 auf
|
||||
* 105 % zu springen wäre ein Knopf, den man zehnmal drückt. Eine Geste
|
||||
* dagegen ist ein Regler: Dort sind Viertelsprünge zu grob, man schießt über
|
||||
* die Größe hinaus, die man eigentlich wollte.
|
||||
* Zuerst war es ein fester Faktor (fünf Prozent *vom aktuellen Wert*).
|
||||
* Rechnerisch ist das die sauberere Größe: Sie fühlt sich unten wie oben
|
||||
* gleich an. Abzulesen war sie aber nicht – die Anzeige sprang um drei
|
||||
* Punkte, dann um vier, dann um sechs. Wer eine Zahl sieht, erwartet ein
|
||||
* Raster, und das hier ist eines.
|
||||
*
|
||||
* Multiplikativ statt in festen Prozentpunkten, weil Zoomen relativ ist: Von
|
||||
* 50 auf 55 % ist ein Zehntel mehr, von 300 auf 305 % ein Sechzigstel. Fünf
|
||||
* Prozent *vom aktuellen Wert* fühlen sich überall gleich an.
|
||||
*
|
||||
* **Achtung beim Ablesen:** Die angezeigte Zahl springt deshalb nicht um
|
||||
* feste fünf Punkte. Bei 65 % sind fünf Prozent gut drei Punkte, bei 200 %
|
||||
* sind es zehn. Erst 1,1 war zu grob (gemessen bei 65 % ein Sprung von sieben
|
||||
* Punkten, kurz darauf von elf).
|
||||
* Der Preis steht dazu: Bei 400 % ist ein Schritt gut ein Prozent, bei 25 %
|
||||
* ein Fünftel. Das gleicht die Geste aus, nicht die Rasterung – siehe unten.
|
||||
*/
|
||||
const zoomFactor = 1.05
|
||||
const zoomStepPercent = 5
|
||||
const minScale = 0.25
|
||||
const maxScale = 4
|
||||
|
||||
@@ -655,13 +650,28 @@ export default function PdfViewer({ url, fileId, searchTerm }: Props) {
|
||||
: [...zoomSteps].reverse().find(s => s < now - 0.001)
|
||||
}
|
||||
|
||||
/** Ein Zehntel weiter, für Finger und Mausrad. Undefined am Anschlag. */
|
||||
/**
|
||||
* Die nächste runde Fünferzahl, für Finger und Mausrad. Undefined am
|
||||
* Anschlag.
|
||||
*
|
||||
* Gerechnet wird in Prozentpunkten und nicht in Bruchteilen: Der Nutzer
|
||||
* liest Prozente, und ein Raster, das nur *fast* auf 70 trifft, ist keins.
|
||||
* Aus einer krummen Ausgangsgröße – „einpassen" ergibt zum Beispiel 65,3 %
|
||||
* – führt der erste Schritt auf die nächste runde Zahl, danach geht es in
|
||||
* Fünfern weiter.
|
||||
*/
|
||||
function nextFine(now: number, dir: 1 | -1): number | undefined {
|
||||
const raw = dir > 0 ? now * zoomFactor : now / zoomFactor
|
||||
// Auf ganze Prozent gerundet: Sonst stünden 71,50000000000001 % in der
|
||||
// Leiste, und zwei Wege zum selben Zoom ergäben zwei verschiedene Zahlen.
|
||||
const next = Math.round(Math.min(maxScale, Math.max(minScale, raw)) * 100) / 100
|
||||
return Math.abs(next - now) < 0.005 ? undefined : next
|
||||
// **Gerundet, bevor gerechnet wird.** 1,1 × 100 ergibt in Fließkomma
|
||||
// 110.00000000000001, und daraus wird die „nächste kleinere Fünferzahl"
|
||||
// wieder 110 – der Zoom blieb dann stehen. Gemessen: Beim Zuziehen kam
|
||||
// genau eine Stufe, danach nichts mehr.
|
||||
const p = Math.round(now * 100 * 1e6) / 1e6
|
||||
const step = zoomStepPercent
|
||||
const raw = dir > 0
|
||||
? Math.floor(p / step + 1) * step
|
||||
: Math.ceil(p / step - 1) * step
|
||||
const next = Math.min(maxScale * 100, Math.max(minScale * 100, raw)) / 100
|
||||
return Math.abs(next - now) < 0.001 ? undefined : next
|
||||
}
|
||||
|
||||
function zoom(dir: 1 | -1) {
|
||||
@@ -750,17 +760,31 @@ export default function PdfViewer({ url, fileId, searchTerm }: Props) {
|
||||
e.preventDefault()
|
||||
const now = distance(e.touches)
|
||||
const ratio = now / base
|
||||
// Die Schwelle folgt der Stufenweite: Wandern die Finger um eine
|
||||
// Stufe auseinander, gibt es eine Stufe. Bliebe sie größer, ließe sich
|
||||
// die feinere Weite gar nicht mehr treffen.
|
||||
const dir: 1 | -1 | 0 = ratio > 1.04 ? 1 : ratio < 0.96 ? -1 : 0
|
||||
const dir: 1 | -1 | 0 = ratio > 1 ? 1 : ratio < 1 ? -1 : 0
|
||||
if (!dir) return
|
||||
pinchBase.current = now
|
||||
const root2 = scrollRef.current
|
||||
if (!root2) return
|
||||
const from = scaleRef.current
|
||||
const next = nextFine(from, dir)
|
||||
if (!next) return
|
||||
|
||||
/**
|
||||
* **Die Finger geben den Maßstab vor, nicht eine feste Schwelle.**
|
||||
*
|
||||
* Fünf Prozentpunkte sind bei 25 % ein Fünftel und bei 400 % ein
|
||||
* Hundertstel. Eine feste Schwelle wäre deshalb an einem Ende zu träge
|
||||
* und am anderen zu nervös: Von 100 auf 400 zu ziehen dauerte ewig,
|
||||
* unten spränge es bei der kleinsten Bewegung.
|
||||
*
|
||||
* Verlangt wird stattdessen genau die Bewegung, die dem nächsten
|
||||
* Schritt entspricht – zwei Prozent Fingerweg als Untergrenze, damit
|
||||
* ein Zittern in der Hand nicht schon zoomt.
|
||||
*/
|
||||
const needed = next / from
|
||||
if (dir > 0 && ratio < Math.max(1.02, needed)) return
|
||||
if (dir < 0 && ratio > Math.min(0.98, needed)) return
|
||||
|
||||
pinchBase.current = now
|
||||
const root2 = scrollRef.current
|
||||
if (!root2) return
|
||||
// Festgehalten wird die Mitte zwischen den Fingern – dieselbe Regel wie
|
||||
// beim Mausrad, nur dass der Zeiger hier aus zwei Punkten entsteht.
|
||||
const r = root2.getBoundingClientRect()
|
||||
|
||||
Reference in new issue
Block a user