Reader: j/k-Scrollen ohne Ramp-up, an feste Kadenz statt Display-Hz gekoppelt (SW v34)
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 17s

Die requestAnimationFrame-Schleife aus dem letzten Fix lief mit der tatsächlichen
Bildwiederholrate des Displays statt einer festen Rate — auf Laptops mit
120/144/165Hz-Panels bedeutete das 2-3x mehr scrollBy-Aufrufe (und damit
Scroll-Listener-Durchläufe) pro Sekunde als beabsichtigt, was sich als Lag
statt als Beschleunigung äußerte. Zurück zu setInterval, aber mit fester
20ms-Kadenz unabhängig vom Display und dt-kompensierter, konstanter
Geschwindigkeit (~1200px/s, kein Ramp-up mehr).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
marwin 2026-08-14 23:50:23 +02:00
parent e70bb881cc
commit 5053d0036a
2 changed files with 20 additions and 23 deletions

View file

@ -2240,13 +2240,10 @@ document.addEventListener('keydown', e => {
const continuousKeys = {'j': 1, 'k': -1};
if (continuousKeys[e.key] !== undefined && !e.repeat) {
e.preventDefault();
const dir = continuousKeys[e.key];
const now = performance.now();
if (_readerScrollDir !== dir) _readerScrollStartTs = now;
_readerScrollDir = dir;
_readerScrollLastTs = now;
if (!_readerScrollRaf) {
_readerScrollRaf = requestAnimationFrame(_readerScrollTick);
_readerScrollDir = continuousKeys[e.key];
if (!_readerScrollInterval) {
_readerScrollLastTs = performance.now();
_readerScrollInterval = setInterval(_readerScrollTick, READER_SCROLL_TICK_MS);
}
}
if (e.key === 'd') { e.preventDefault(); contentEl.scrollBy({top: large / 2, behavior: 'smooth'}); }
@ -2266,8 +2263,8 @@ document.addEventListener('keydown', e => {
document.addEventListener('keyup', e => {
if (e.key === 'j' || e.key === 'k') {
if (_readerScrollRaf) cancelAnimationFrame(_readerScrollRaf);
_readerScrollRaf = null;
clearInterval(_readerScrollInterval);
_readerScrollInterval = null;
_readerScrollDir = 0;
}
});
@ -3189,21 +3186,21 @@ let pdfTotalPages = 0;
let _pdfPageTextBoxCache = {};
let _pdfRenderGen = 0;
let _touchStartX = 0;
let _readerScrollRaf = null;
let _readerScrollInterval = null;
let _readerScrollDir = 0;
let _readerScrollStartTs = 0;
let _readerScrollLastTs = 0;
const READER_SCROLL_MIN_SPEED = 700; // px/s right as j/k is pressed
const READER_SCROLL_MAX_SPEED = 1800; // px/s once ramped up
const READER_SCROLL_RAMP_MS = 300; // time to reach max speed
function _readerScrollTick(ts) {
const dt = ts - _readerScrollLastTs;
_readerScrollLastTs = ts;
const held = ts - _readerScrollStartTs;
const t = Math.min(held / READER_SCROLL_RAMP_MS, 1);
const speed = READER_SCROLL_MIN_SPEED + (READER_SCROLL_MAX_SPEED - READER_SCROLL_MIN_SPEED) * t;
$('reader-content')?.scrollBy({top: _readerScrollDir * speed * dt / 1000});
_readerScrollRaf = requestAnimationFrame(_readerScrollTick);
const READER_SCROLL_SPEED = 1200; // constant px/s, no ramp-up
const READER_SCROLL_TICK_MS = 20; // fixed cadence, independent of the
// display's refresh rate — rAF runs at
// that rate (120/144/165Hz on plenty of
// laptops), which was driving 2-3x more
// scrollBy + scroll-listener work per
// second than intended and read as lag.
function _readerScrollTick() {
const now = performance.now();
const dt = now - _readerScrollLastTs;
_readerScrollLastTs = now;
$('reader-content')?.scrollBy({top: Math.round(_readerScrollDir * READER_SCROLL_SPEED * dt / 1000)});
}
let _pinchStartDist = 0;
let _pinchStartZoom = 100;

View file

@ -2,7 +2,7 @@
* diora service worker caches the app shell for offline use.
*/
const CACHE = 'diora-v33';
const CACHE = 'diora-v34';
const PODCAST_CACHE = 'diora-podcast-v1';
const SHELL = [
'/static/css/app.css',