Skip to content

Fix initial page drift in PDF scroll mode#206

Open
alea12 wants to merge 1 commit into
mainfrom
NOREF/fix-pdf-scroll-initial-page-drift
Open

Fix initial page drift in PDF scroll mode#206
alea12 wants to merge 1 commit into
mainfrom
NOREF/fix-pdf-scroll-initial-page-drift

Conversation

@alea12

@alea12 alea12 commented Jul 15, 2026

Copy link
Copy Markdown
Member

Problem

When the reader opens with a start page in scroll mode (e.g. a manifest readingOrder href with ?start=00000262), the viewer initially shows the requested page number but then drifts to a later page (262 → 274/345 depending on viewport) once the PDF finishes loading.

Cause

The scroll effect performed a single scrollTo to the target page as soon as the first page rendered (state.rendered). At that moment page slot heights are still provisional: placeholders are later swapped for rendered pages, and the fit-mode resize (RESIZE_PAGE) changes every page's height. The layout shift after the one-shot scroll leaves the viewport several pages past the target (measured ~5,900px = 12.4 pages past page 262 with a 480px slot pitch). PAGE_IN_VIEW then reports the wrong visible page and, because it sets isInViewUpdate, the corrective re-scroll is skipped.

The drift amount depends on the ratio between provisional and final slot heights, which is why different viewports land on different pages.

Fix

Keep the target page anchored instead of scrolling once:

  • Re-check the target page's position every animation frame and correct the scroll offset whenever it moves (layout shifts from page loads / resizes).
  • Stop when the position has been stable for ANCHOR_SETTLE_MS (1s), or immediately when the user interacts with the reader (wheel / touchstart / mousedown / keydown), so user scrolling is never hijacked.
  • Each correction refreshes lastProgrammaticNavAt / lastVisiblePage / ratios (same as beginProgrammaticNavigation), so PAGE_IN_VIEW cannot override the target mid-anchoring.

Verification

Tested against DRB enhanced-search locally with /item/...?previewItemId=25443668&previewPage=00000262 (516-page PDF):

  • Before: toolbar showed 262, then jumped to 274 after load; scrollTop overshot page 262's offset by 12.4 pages.
  • After: viewer settles exactly on page 262 (scrollTop == page 262 offset, delta 0px) and stays there; manual scrolling afterwards is not pulled back.
  • jest --ci: 9 suites / 27 tests pass.

🤖 Generated with Claude Code

In scrolling mode, navigating to a start page performed a single
scrollTo as soon as the first page rendered. While the PDF streams in,
placeholders are swapped for rendered pages and the fit-mode resize
changes every page's height, shifting the target page after the scroll
already happened. The viewport then landed a dozen or more pages past
the requested one, and PAGE_IN_VIEW locked that wrong page number in.

Keep the target page anchored by re-checking its position every frame
and correcting the scroll offset until the layout has been stable for
ANCHOR_SETTLE_MS, or until the user interacts with the reader (wheel,
touch, mouse, or keyboard). Each correction also refreshes the
programmatic-navigation guard so PAGE_IN_VIEW does not override the
target page mid-anchoring.
@alea12
alea12 requested a review from jackiequach as a code owner July 15, 2026 18:00
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web-reader Ready Ready Preview, Comment Jul 15, 2026 6:00pm

Request Review

@alea12
alea12 removed the request for review from jackiequach July 15, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant