diff --git a/frontend/js/videos.js b/frontend/js/videos.js index 6c521b3..2711902 100644 --- a/frontend/js/videos.js +++ b/frontend/js/videos.js @@ -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.