/ shared / utils / src / try-scroll.ts
try-scroll.ts
 1  import type { Logger } from '@amp/web-apps-logger';
 2  export interface ScrollableElement {
 3      scrollTop: number;
 4      scrollHeight: number;
 5      offsetHeight: number;
 6  }
 7  
 8  // Global is okay here as this only runs in the browser
 9  let nextTry: number | null = null;
10  
11  export function tryScroll(
12      log: Logger,
13      getScrollablePageElement: Function,
14      scrollY: number,
15  ): void {
16      let tries = 0;
17  
18      if (nextTry !== null) {
19          window.cancelAnimationFrame(nextTry);
20      }
21  
22      nextTry = window.requestAnimationFrame(function doNextTry() {
23          // At 16ms per frame, this is 1600ms
24          // See: https://github.com/DockYard/ember-router-scroll/blob/2f17728f/addon/services/router-scroll.js#L56
25          if (++tries >= 100) {
26              log.warn("wasn't able to restore scroll within 100 frames");
27              nextTry = null;
28              return;
29          }
30  
31          let element = getScrollablePageElement();
32          if (!element) {
33              log.warn(
34                  'could not restore scroll: the scrollable element is missing',
35              );
36              return;
37          }
38          const { scrollHeight, offsetHeight } = element;
39  
40          // Only scroll once we're able to get a full screen of content when
41          // scrollTop is set to scrollY
42          //
43          // +16 is a bit of a fudge factor to count for imperfections in
44          // features like lazy loading. If the scroll position to restore is
45          // the very bottom of the page, then scrollY + offsetHeight must be
46          // exactly scrollHeight. But if lazy loading components (for example)
47          // cause the page to grow by a few pixels, then this will never hold.
48          // Thus, we fudge by a few pixels to be more forgiving in this scenario.
49          const canScroll = scrollY + offsetHeight <= scrollHeight + 16;
50  
51          if (!canScroll) {
52              log.info('page is not tall enough for scroll yet', {
53                  scrollHeight,
54                  offsetHeight,
55              });
56  
57              nextTry = window.requestAnimationFrame(doNextTry);
58              return;
59          }
60  
61          element.scrollTop = scrollY;
62          log.info('scroll restored to', scrollY);
63          nextTry = null;
64      });
65  }