Don't re-anchor the grid on height-only viewport changes

The settle window I added for rotation also ran for every `resize`, and
on a phone the URL bar collapsing during a fast flick is exactly that.
It re-asserted an anchor captured before the flick, so a scrollTo landed
mid-momentum and stopped the scroll dead.

Only a width change (or an orientationchange) can move a card: positions
are absolute pixels in the grid's own space, so a height-only resize
leaves both the layout and the scroll position correct and needs no
restore at all. The one exception kept is a scrollbar appearing on
desktop, which changes the grid's inner width while window.innerWidth
stays put -- that re-packs, but only when the columns actually moved.

Modelled the failure in headless Chrome (height change mid-flick, scroll
continuing): the flick was yanked back 173px before, and now runs on to
where it was headed. Rotation still re-anchors, and a fast scroll with 80
videos loaded holds 60fps (median 16.7ms/frame, max 17.2ms, no frame
over 32ms).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
This commit is contained in:
Simon
2026-09-05 15:15:44 +00:00
parent d7086ead27
commit a9893068cd

View File

@@ -866,8 +866,33 @@ App.videos = App.videos || {};
return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable;
};
const beginSettle = function() {
let settleWidth = 0;
const beginSettle = function(event) {
if (isTypingTarget()) return;
// Only a width change moves cards: their positions are absolute
// pixels in the grid's own space, so a height-only resize -- the
// mobile URL bar collapsing as you scroll, most of all -- leaves the
// layout (and therefore the scroll position) exactly right. Settling
// on those was actively harmful: a URL-bar resize landing mid-flick
// re-asserted an anchor captured before the flick and yanked the
// page back, killing the momentum scroll.
const width = window.innerWidth || 0;
const widthChanged = width !== settleWidth;
settleWidth = width;
const rotating = !!(event && event.type === 'orientationchange');
if (!widthChanged && !rotating) {
// One exception: a scrollbar appearing/disappearing changes the
// grid's inner width while window.innerWidth stays put. Re-pack
// if the columns really moved (relayout keeps the reader's
// place); otherwise leave the scroll completely alone.
const prevCols = cols;
const prevColWidth = colWidth;
if (measureMetrics() && (cols !== prevCols || Math.abs(colWidth - prevColWidth) > 0.5)) {
relayout();
}
return;
}
if (!heldAnchor) heldAnchor = lastAnchor;
settleTimers.forEach(clearTimeout);
settleTimers = SETTLE_TICKS.map((ms) => setTimeout(settleTick, ms));
@@ -878,6 +903,7 @@ App.videos = App.videos || {};
if (!cols) measureMetrics();
if (initialized) return;
initialized = true;
settleWidth = window.innerWidth || 0;
window.addEventListener('scroll', scheduleUpdate, { passive: true });
// orientationchange fires first (before the viewport metrics change),
// which is exactly when the pre-rotation anchor is still valid.