Files
apps.apple.com/shared/utils/src/try-scroll.ts
2025-11-04 05:03:50 +08:00

66 lines
2.1 KiB
TypeScript

import type { Logger } from '@amp/web-apps-logger';
export interface ScrollableElement {
scrollTop: number;
scrollHeight: number;
offsetHeight: number;
}
// Global is okay here as this only runs in the browser
let nextTry: number | null = null;
export function tryScroll(
log: Logger,
getScrollablePageElement: Function,
scrollY: number,
): void {
let tries = 0;
if (nextTry !== null) {
window.cancelAnimationFrame(nextTry);
}
nextTry = window.requestAnimationFrame(function doNextTry() {
// At 16ms per frame, this is 1600ms
// See: https://github.com/DockYard/ember-router-scroll/blob/2f17728f/addon/services/router-scroll.js#L56
if (++tries >= 100) {
log.warn("wasn't able to restore scroll within 100 frames");
nextTry = null;
return;
}
let element = getScrollablePageElement();
if (!element) {
log.warn(
'could not restore scroll: the scrollable element is missing',
);
return;
}
const { scrollHeight, offsetHeight } = element;
// Only scroll once we're able to get a full screen of content when
// scrollTop is set to scrollY
//
// +16 is a bit of a fudge factor to count for imperfections in
// features like lazy loading. If the scroll position to restore is
// the very bottom of the page, then scrollY + offsetHeight must be
// exactly scrollHeight. But if lazy loading components (for example)
// cause the page to grow by a few pixels, then this will never hold.
// Thus, we fudge by a few pixels to be more forgiving in this scenario.
const canScroll = scrollY + offsetHeight <= scrollHeight + 16;
if (!canScroll) {
log.info('page is not tall enough for scroll yet', {
scrollHeight,
offsetHeight,
});
nextTry = window.requestAnimationFrame(doNextTry);
return;
}
element.scrollTop = scrollY;
log.info('scroll restored to', scrollY);
nextTry = null;
});
}