svelte-intersection-observer icon indicating copy to clipboard operation
svelte-intersection-observer copied to clipboard

feat: add `MultipleIntersectionObserver`

Open metonym opened this issue 9 months ago • 0 comments

A common scenario is to have multiple elements for observing. Currently, the library only allows one element to be observed at a time. For large number of items, this is less performant than using a single observer for multiple elements.

This introduces MultipleIntersectionObserver, which allows multiple elements to be observed by a single instance. The ergonomics of using it is a bit different, in that an array is provided and a Map returned.

The most basic, proposed usage is as follows:

<script>
  import { MultipleIntersectionObserver } from "svelte-intersection-observer";

  let ref1;
  let ref2;

  $: elements = [ref1, ref2];
</script>

<header />

<MultipleIntersectionObserver {elements} let:elementIntersections>
  {#each elements as element, index}
    {@const visible = elementIntersections.get(element)}

    <div bind:this={element} class:visible>
      Item {index + 1}
    </div>
  {/each}
</MultipleIntersectionObserver>

Another example is binding to the map directly to re-use state elsewhere in the component:

<script>
  import { MultipleIntersectionObserver } from "svelte-intersection-observer";

  let ref1;
  let ref2;
  let elementIntersections = new Map();

  $: elements = [ref1, ref2];
  $: anyItemVisible = Array.from(elementIntersections).some(
    (visible) => visible,
  );
</script>

<header class:intersecting={anyItemVisible}>
  {#each elements as element, index}
    {@const visible = elementIntersections.get(element)}
    {#if visible}
      Item {index + 1} visible
    {/if}
  {/each}
</header>

<MultipleIntersectionObserver
  {elements}
  bind:elementIntersections
  let:elementIntersections
>
  {#each elements as element, index}
    {@const visible = elementIntersections.get(element)}

    <div bind:this={element} class:visible>
      Item {index + 1} ({visible})
    </div>
  {/each}
</MultipleIntersectionObserver>

However, it's simpler to wrap the sibling element with the MultipleIntersectionObserver instance, and use the destructed let:elementIntersections directly instead of binding to it.

<script>
  import { MultipleIntersectionObserver } from "svelte-intersection-observer";

  let ref1;
  let ref2;

  $: elements = [ref1, ref2];
</script>

<MultipleIntersectionObserver {elements} let:elementIntersections>
  {@const anyItemVisible = Array.from(elementIntersections).some(
    (visible) => visible,
  )}
  <header class:intersecting={anyItemVisible}>
    {#each elements as element, index}
      {@const visible = elementIntersections.get(element)}
      {#if visible}
        Item {index + 1} visible
      {/if}
    {/each}
  </header>

  {#each elements as element, index}
    {@const visible = elementIntersections.get(element)}

    <div bind:this={element} class:visible>
      Item {index + 1} ({visible})
    </div>
  {/each}
</MultipleIntersectionObserver>

metonym avatar Mar 02 '25 01:03 metonym