Der Regler gehörte der Uhr, nicht dem Finger

`zeitAuffrischen` schrieb `zeitleiste.value` bei jedem `timeupdate` neu —
viermal je Sekunde, auch mitten im Ziehen. Das hatte zwei Gesichter, und
nur das harmlosere war zu sehen: der Knopf sprang unter dem Finger an die
laufende Stelle zurück, und nach dem Loslassen noch einmal, weil ein
Sprung einen Augenblick braucht und der Spieler in dem weiter die alte
Stelle meldet.

Das andere ist schlimmer. Gemessen an einer Nachbildung mit 20-ms-Takt:
gezogen auf 113, und `onchange` las 7 — der Takt hatte `value` überschrieben,
bevor der Browser den Zug festschreiben konnte. Es sprang also nicht nur
zurück, es sprang an die falsche Stelle. Genau deshalb half Festhalten: es
schiebt den letzten `input` näher an den `change`.

Jetzt gehört der Regler dem Zeiger, solange einer darauf liegt, und danach
dem Sprungziel, bis der Spieler dort angekommen ist. Mit Notbremse nach
dreissig Sekunden, damit eine Leiste nie stehen bleibt, wenn ein neu
gebauter Strom woanders herauskommt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-13 16:39:28 +02:00
co-authored by Claude Opus 5
parent 0186087051
commit d152ac3795
+46 -4
View File
@@ -763,17 +763,59 @@ export function spieler(anfrage: Spieleranfrage): void {
zeigeFehler(videofehler());
};
zeitleiste.oninput = () => { stand.textContent = dauer(Number(zeitleiste.value)); };
zeitleiste.onchange = () => void springen(Number(zeitleiste.value));
/** Ob der Regler gerade dem Zeiger gehört und nicht der Uhr. */
let amRegler = false;
/** Wohin gesprungen wurde — bis der Spieler dort angekommen ist. */
let sprungziel: number | null = null;
let sprungAb = 0;
zeitleiste.oninput = () => {
// Auch Tastatur und Finger kommen hier an, nicht nur die Maus.
amRegler = true;
stand.textContent = dauer(Number(zeitleiste.value));
};
zeitleiste.onchange = () => {
amRegler = false;
const ziel = Number(zeitleiste.value);
sprungziel = ziel;
sprungAb = performance.now();
void springen(ziel);
};
// Ein abgebrochener Zeiger meldet kein `change`. Ohne das bliebe der
// Regler stehen, bis jemand ihn wieder anfasst.
zeitleiste.onpointercancel = () => { amRegler = false; };
/** Die Gesamtlänge des Films — nicht die des Stroms. */
const gesamt = anfrage.laenge ?? 0;
function zeitAuffrischen(): void {
const jetzt = filmzeit();
let jetzt = filmzeit();
// **Der Regler gehört dem Zeiger, nicht der Uhr.**
//
// `timeupdate` kommt viermal je Sekunde und schrieb `value` jedes Mal
// neu — auch mitten im Ziehen. Der Knopf sprang dadurch unter dem
// Finger an die laufende Stelle zurück, und nur wer festhielt und
// gegenhielt, kam irgendwo an. Gleich danach dasselbe noch einmal: ein
// Sprung braucht einen Augenblick, und in dem meldet der Spieler
// weiter die alte Stelle. Ungeprüft übernommen zieht das den Regler
// zurück, kurz bevor er nach vorn springt.
if (amRegler) {
jetzt = Number(zeitleiste.value);
} else if (sprungziel !== null) {
// Die Notbremse: landet der Sprung nie — weil der Strom neu gebaut
// wird und woanders herauskommt —, bliebe die Leiste sonst für
// immer stehen.
if (Math.abs(jetzt - sprungziel) < 1.5 || performance.now() - sprungAb > 30_000) {
sprungziel = null;
} else {
jetzt = sprungziel;
}
}
const laenge = gesamt > 0 ? gesamt : vorlauf + (Number.isFinite(video.duration) ? video.duration : 0);
zeitleiste.max = String(Math.max(1, laenge));
zeitleiste.value = String(jetzt);
if (!amRegler) zeitleiste.value = String(jetzt);
stand.textContent = dauer(jetzt);
rest.textContent = laenge > jetzt ? `−${dauer(laenge - jetzt)}` : '';