Intersection Observer

section icon home home icon

Svelte Intersection Observer lazy loading

link icon

Using IntersectionObserver via the svlete-intersection-observer library to lazy-load content once and trigger changes to a content block when it comes into view (intersects), while also passing intersecting element as prop to a sticky element on the page via a Svelte store.

Main points

  • Use two separate intersection observers
  • We need two intersection observers if we want to do lazy loading and change the element somehow after is has been lazy loaded. If the elements don't need to lazy-load or if they don't need to change in any way after they have first loaded, then we only need one IntersectionObserver.
  • The first (outer) intersection observer is for lazy loading. It observers each element and if the element enters the viewport (threshold="0"), we load the element. This happens only once (once="true"), which makes sense because we don't need to lazy-load more than once. The lazy loading bit is achieved by adding the condition if intersecting as then we will only display the DOM element if it intersects the viewport.
  • In this particular example there is no need to use lazy loading actually, but if we were loading really heavy chunks of content, e.g. HTML5 canvas elements with thousands of shapes drawn in each of them, it would help performance to lazy-load each canvas as it comes into view, rather than trying to load all of them at once on page load/component mount.
  • The second (inner) intersection observer is there to affect a change to the already lazy-loaded element based on some different intersecting condition. In this case, if the element becomes 70% visible (threshold="0.7"), it will change colour.
    (See .style('background', d => intersecting ? '#ee3e3e' : '#6b6065') in ContentElement.)
    Sidenote: The threshold prop of the IntersectionObserver here is not the viewport threshold, but the proportion (0 to 1) of the intersecting element that has to be visible in the viewport to trigger the intersecting prop to become true.
  • To update the content of the sticky element:
    • - Initialise a currentElNum variable as a svelte store.
    • - Set the value of currentElNum to the id corresponding to the data entry bound to that element each time the second IntersectionObserver's intersection condition is satisfied. In other words, when an element is 70% visible in the viewport, currentElNum gets updated with the id for the data entry bound to that element.
    • - Then the StickyElement imports currentElNum and its content gets dynamically updated based on it.
  • Beware that this is buggy if the height of the intersecting elements is small enough that multiple of them fit into the viewport.

Code highlights


I am a sticky element and I update based on the intersecting element, which is:

-1

loading...
loading...
loading...
loading...
loading...

Vanilla Intersection Observer

link icon

Using just the built-in Intersection Observer API, affecting a change in the DOM when an element intersects the viewport.

Main points

  • The built-in IntersectionObserver class takes in two parameters, an observerCallback and observerOptions.
  • observerOptions are things like the threshold at which an element is considered intersecting (as a proportion of the total height of the element). These are static properties.
  • observerCallback determines what happens when the intersection event occurs. In our case, we define a inView variable, which keeps track if the element is in view and update it from that callback function.
    *Note that if we then wanted to run any logic that depends on inView, it would need to be wrapped in a reactive declaration. For example, if we then wanted to draw a D3 graph, we'd need to make the code reactive, just like in other examples here.
  • Then we instantiate the IntersectionObserver and start observing the desired element from onMount. This is to make sure that the element we are observing has been mounted.
  • Finally, we use a class of active on the observed element, where active is defined in CSS and determines how the appearance of the changes when inView = true.
    *Note that the note above about inView only being able to affect a change if it is wrapped in a reactive statement doesn't apply in this case. This is because Svelte-native DOM changes (e.g. DOM-events from Svelte like on:click or class conditions like class:active etc.) are reactive by design.

Code highlights


When 50% of the element below is into the viewport, it will change colour. Scroll up and down the page to see.