rotation edge case

This commit is contained in:
Simon
2026-06-23 21:42:29 +00:00
parent f5bb33521e
commit 5aa95e90d4
3 changed files with 98 additions and 8 deletions

View File

@@ -32,6 +32,16 @@ App.feed = App.feed || {};
let scrollBound = false;
let scrollRaf = null;
// The slide height under which the current scroll position / top-spacer were
// last laid out. On an orientation change the viewport (and therefore the
// slide height) changes, which makes the browser re-snap the scroll-snap
// container and fire scroll events with positions that no longer map to the
// active slide. We compare against this so onScroll can ignore those
// resize-induced scroll events and let onResize realign to the active video
// instead -- the active slide must never change just because the device was
// rotated. It's updated only when we (re)align the scroll position ourselves.
let lastSlideHeight = 0;
// HUD auto-hide: the reels HUD fades out after this much inactivity and
// reappears on any pointer movement / tap / scroll. Buttons keep their
// pointer-events while hidden, so they stay clickable even when invisible.
@@ -385,6 +395,8 @@ App.feed = App.feed || {};
const clamped = clampIndex(index);
if (clamped < 0) return;
state.feedActiveIndex = clamped;
const activeVideo = (state.loadedVideos || [])[clamped];
state.feedActiveVideoId = activeVideo ? activeVideo.id : null;
syncWindow(clamped);
@@ -421,7 +433,14 @@ App.feed = App.feed || {};
scrollRaf = null;
const scroller = getScroller();
if (!scroller) return;
const index = clampIndex(Math.round(scroller.scrollTop / slideHeight()));
const h = slideHeight();
// A scroll that fires while the slide height differs from what the
// current layout was aligned to is a side effect of a viewport
// change (e.g. an orientation switch re-snapping the container), not
// a real user swipe. Ignore it and let onResize re-anchor to the
// active video, so rotating the device never jumps to another slide.
if (h !== lastSlideHeight) return;
const index = clampIndex(Math.round(scroller.scrollTop / h));
if (index < 0) return;
if (index !== state.feedActiveIndex) {
setActive(index);
@@ -429,18 +448,42 @@ App.feed = App.feed || {};
});
};
const onResize = function() {
if (!state.feedOpen || state.feedActiveIndex < 0) return;
// Re-anchors the scroll position on the currently active video after the
// viewport changes. The active slide is resolved by id (not by a possibly
// stale scroll position) so an orientation change always keeps the same
// video playing/focused rather than snapping to a neighbour.
const realignToActive = function() {
const total = (state.loadedVideos || []).length;
if (total === 0) return;
let index = state.feedActiveIndex;
if (state.feedActiveVideoId != null) {
const found = (state.loadedVideos || [])
.findIndex((v) => String(v.id) === String(state.feedActiveVideoId));
if (found >= 0) index = found;
}
index = clampIndex(index);
if (index < 0) return;
state.feedActiveIndex = index;
const h = slideHeight();
const start = Math.max(0, state.feedActiveIndex - HISTORY_COUNT);
const start = Math.max(0, index - HISTORY_COUNT);
const spacer = getTopSpacer();
if (spacer) spacer.style.height = `${start * h}px`;
const scroller = getScroller();
if (scroller) scroller.scrollTop = state.feedActiveIndex * h;
const activeSlide = slidesByIndex.get(state.feedActiveIndex);
if (scroller) scroller.scrollTop = index * h;
lastSlideHeight = h;
const activeSlide = slidesByIndex.get(index);
if (activeSlide) measureFeedTitle(activeSlide);
};
const onResize = function() {
if (!state.feedOpen || state.feedActiveIndex < 0) return;
realignToActive();
// Orientation changes can settle over more than one frame (the visual
// viewport and the scroll-snap re-anchor in stages); realign again once
// layout has settled so the active video stays put either way.
requestAnimationFrame(realignToActive);
};
App.feed.isOpen = function() {
return !!state.feedOpen;
};
@@ -461,6 +504,8 @@ App.feed = App.feed || {};
slidesByIndex.clear();
resumeTimes.clear();
state.feedActiveIndex = -1;
state.feedActiveVideoId = null;
lastSlideHeight = 0;
const spacer = getTopSpacer();
if (spacer) spacer.style.height = '0px';
const scroller = getScroller();
@@ -506,7 +551,8 @@ App.feed = App.feed || {};
// Force a fresh activation even if the index happens to match.
state.feedActiveIndex = -1;
setActive(startIndex);
scroller.scrollTop = startIndex * slideHeight();
lastSlideHeight = slideHeight();
scroller.scrollTop = startIndex * lastSlideHeight;
App.feed.updateToggleButton();
App.feed.updateMuteButton();