/ shared / components / src / components / Artwork / loaders / LazyLoader.svelte
LazyLoader.svelte
 1  <!--
 2      LazyLoader Component
 3      This component provides loading="lazy"
 4      functionality for browsers that do not support it.
 5      It uses Intersection Observers to evaluate
 6      if an image needs to be loaded.
 7  
 8      DO NOT USE DIRECTLY use LoaderSelector
 9  -->
10  <script context="module" lang="ts">
11      import { get } from 'svelte/store';
12      import { shouldUseLazyLoader } from '@amp/web-app-components/src/components/Artwork/constants';
13      import { createArtworkLoaderStore } from '@amp/web-app-components/src/components/Artwork/stores/artworkLoader';
14      import type { ArtworkLoaderStore } from '@amp/web-app-components/src/components/Artwork/stores/artworkLoader';
15      import { getRafQueue } from '@amp/web-app-components/src/utils/rafQueue';
16  
17      const rafQueue = getRafQueue();
18  
19      let artworkLookupTable: ArtworkLoaderStore | null = null;
20      let observer: IntersectionObserver | null = null;
21  
22      const setupObserver = () => {
23          let options = {
24              root: null, // go off viewport
25              rootMargin: '0px',
26              threshold: 0.0,
27          };
28  
29          return new IntersectionObserver((entries) => {
30              entries.forEach((item) => {
31                  rafQueue.add(() => {
32                      const storeValue = get(artworkLookupTable);
33                      const isItemAlreadyVisible = storeValue.get(item.target);
34                      if (!isItemAlreadyVisible) {
35                          artworkLookupTable.addEntry(
36                              item.target,
37                              item.isIntersecting,
38                          );
39                      }
40                  });
41              });
42          }, options);
43      };
44      if (shouldUseLazyLoader) {
45          observer = setupObserver();
46          artworkLookupTable = createArtworkLoaderStore();
47      }
48  </script>
49  
50  <script lang="ts">
51      import { onDestroy } from 'svelte';
52  
53      let isSubscribed = false;
54  
55      let container: Element;
56      let isVisible: boolean = false;
57      let unsubscribeToStore: () => void = () => {};
58  
59      const cleanup = () => {
60          unsubscribeToStore();
61          observer.unobserve(container);
62          artworkLookupTable.cleanupEntry(container);
63      };
64  
65      $: {
66          if (isVisible && isSubscribed) {
67              cleanup();
68              isSubscribed = false;
69          }
70      }
71  
72      export function onSlotMount(artworkComponent: Element) {
73          container = artworkComponent;
74          isSubscribed = true;
75          observer.observe(container);
76  
77          unsubscribeToStore = artworkLookupTable.subscribe((map) => {
78              isVisible = map.get(container);
79          });
80      }
81  
82      onDestroy(() => {
83          if (isSubscribed) {
84              cleanup();
85          }
86      });
87  </script>
88  
89  <slot {isVisible} />