Skip to content

Keep the whole photo on screen after leaving fullscreen on iPad - #384

Merged
lstein merged 1 commit into
masterfrom
lstein/fix/ipad-windowed-image-cutoff
Aug 23, 2026
Merged

Keep the whole photo on screen after leaving fullscreen on iPad#384
lstein merged 1 commit into
masterfrom
lstein/fix/ipad-windowed-image-cutoff

Conversation

@lstein

@lstein lstein commented Aug 23, 2026

Copy link
Copy Markdown
Owner

The bug

On iPad Chrome, viewing an image in swiper mode, entering fullscreen and returning to windowed mode cuts ~40px off the bottom of the photo, and neither pinch nor rotating the tablet brings it back. It only shows in landscape.

Why

Same disease #382 fixed for the bottom panels: iPadOS keeps the layout viewport stranded at its fullscreen size after the exit. #382 translated the panels back into view, but the swiper container and slide images are sized with 100dvh — which resolves against that same stranded viewport — so the bottom ~40px (the returning browser chrome's height) of the photo hangs off the tablet. Landscape is the orientation where the contain-fit photo actually touches the top and bottom edges; in portrait the loss falls inside the letterbox bars, which is why it looked landscape-only.

The fix

panel-anchor.js already measures the overshoot (with the keyboard hold, pinch guard, chrome-height floor, and settle resample). It now also publishes the visible height as a --visible-viewport-height custom property on the root element, and the three 100dvh rules in swiper.css consume it with 100dvh as fallback. Where the viewports agree the property is never set and nothing changes. Swiper picks up the container resize through its default ResizeObserver.

An adversarial review of the first cut found two moments where the sync recomputed when it must not — previously they only twitched the small panels, but sizing the photo by the correction would have made them a visible full-image bounce. Both now hold the current correction instead of recomputing:

  • The blur edge of a text field. On iPad the blur lands while the keyboard is still fully up (hiding the search panel blurs its input first; the keyboard collapses after), so the first post-blur sample reads the keyboard as a ~360px overshoot. The hold now runs from focus until 700ms past blur, and the expiry resamples the truth.
  • Pinch-zoom. Clearing the correction when scale crosses 1.01 grew the container back to the stranded 100dvh under the user's fingers (and dropped the panels off-screen again). Held instead; zoom-out resyncs via the viewport resize event.

Holding rather than recomputing also stops a rotate-with-the-keyboard-up from publishing a height that mixes the held overshoot with a fresh clientHeight.

Known follow-up, deliberately not in this PR: grid view sizes its rows from window.innerHeight and would still under-show the bottom row on a stranded viewport.

Tests

panel-anchor.test.js: property published with the correction, unset when the viewports agree, cleared when they re-converge, the keyboard hold across focus / blur edge / collapse, the pinch hold and release, and the rotate-with-keyboard case. Frontend 665 passed, lint and format clean.

Needs on-device verification on the reporter's iPad (Chrome, landscape): fullscreen → windowed with a landscape photo, then text search open/submit, and pinch-zoom while stranded.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KLxwVusSvKkfdJS3g9QbB9

PR #382 pulled the bottom panels back into the visible area after iPadOS
strands the layout viewport at its fullscreen size, but the photo itself has
the same problem: the swiper container and slide images are sized with 100dvh,
which resolves against that same stranded layout viewport. After leaving
fullscreen the bottom ~40px of the image — the height of the returning browser
chrome — hangs off the bottom of the tablet, and neither pinch nor rotation
brings it back. Only landscape shows it, because that is the orientation in
which a contain-fit photo actually touches the top and bottom edges; in
portrait the loss falls inside the letterbox bars.

panel-anchor.js already measures the overshoot; it now also publishes the
visible height as a --visible-viewport-height custom property on the root
element, and the three 100dvh sizing rules in swiper.css consume it with
100dvh as the fallback. Where the viewports agree the property is never set
and the CSS behaves exactly as before. Swiper notices the container resize
through its default ResizeObserver, so no explicit update call is needed.

Sizing the photo by the correction raises the stakes on two moments where the
sync previously misfired against the small panels and now would visibly bounce
the whole image, so both hold the current correction instead of recomputing:

- The blur edge of a text field. On iPad the blur arrives while the software
  keyboard is still fully on screen — hiding the search panel blurs its input
  first, the keyboard collapses after — so the first resyncs after blur read
  the keyboard as a ~360px overshoot. The hold now extends from focus until
  700ms past blur, and the expiry resamples the truth.
- Pinch-zoom. Clearing the correction the moment scale crosses 1.01 grew the
  container back to the stranded 100dvh under the user's fingers (and dropped
  the panels back off-screen). The overshoot has not gone away because the
  user zoomed, so it is held; zoom-out resyncs via the viewport resize.

Holding instead of recomputing also stops a rotate-with-keyboard-up from
publishing a height that mixes the held overshoot with a fresh clientHeight.

Not touched: grid view sizes its rows from window.innerHeight and recomputes
only on its own events, so it still under-shows the bottom row on a stranded
viewport — a follow-up, not part of this fix. The 100vh modals are centered
and unaffected in practice.

Tests: panel-anchor.test.js covers the property being published with the
correction, staying unset when the viewports agree, clearing when they
re-converge, the keyboard hold across focus, blur edge and collapse, the
pinch hold and its release, and the rotate-with-keyboard case. Frontend 665
passed, lint and format clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLxwVusSvKkfdJS3g9QbB9
@lstein
lstein merged commit 923d307 into master Aug 23, 2026
10 checks passed
@lstein
lstein deleted the lstein/fix/ipad-windowed-image-cutoff branch August 23, 2026 15:59
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