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:
@@ -866,8 +866,33 @@ App.videos = App.videos || {};
|
|||||||
return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable;
|
return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable;
|
||||||
};
|
};
|
||||||
|
|
||||||
const beginSettle = function() {
|
let settleWidth = 0;
|
||||||
|
|
||||||
|
const beginSettle = function(event) {
|
||||||
if (isTypingTarget()) return;
|
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;
|
if (!heldAnchor) heldAnchor = lastAnchor;
|
||||||
settleTimers.forEach(clearTimeout);
|
settleTimers.forEach(clearTimeout);
|
||||||
settleTimers = SETTLE_TICKS.map((ms) => setTimeout(settleTick, ms));
|
settleTimers = SETTLE_TICKS.map((ms) => setTimeout(settleTick, ms));
|
||||||
@@ -878,6 +903,7 @@ App.videos = App.videos || {};
|
|||||||
if (!cols) measureMetrics();
|
if (!cols) measureMetrics();
|
||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
settleWidth = window.innerWidth || 0;
|
||||||
window.addEventListener('scroll', scheduleUpdate, { passive: true });
|
window.addEventListener('scroll', scheduleUpdate, { passive: true });
|
||||||
// orientationchange fires first (before the viewport metrics change),
|
// orientationchange fires first (before the viewport metrics change),
|
||||||
// which is exactly when the pre-rotation anchor is still valid.
|
// which is exactly when the pre-rotation anchor is still valid.
|
||||||
|
|||||||
Reference in New Issue
Block a user