A Svelte action for intersection observer
npm i svelte-use-io
·
Complaint box: @dereknguyen10npm i svelte-use-io
·
Complaint box: @dereknguyen10npm i svelte-use-io
# or yarn add svelte-use-io
# or pnpm i svelte-use-io
Passing directly to html elements:
<script>
import { onDestroy } from 'svelte';
import { createObserver } from 'svelte-use-io';
const { observer } = createObserver();
const doStuff = ({ detail }) => console.log({ detail });
// { detail: IntersectionObserverEntry }
</script>
<ul>
{#each Array.from({ length: 6 }, (_, i) => i + 1) as i (i)}
<li use:observer on:intersecting="{doStuff}">Item {i}</li>
{/each}
</ul>
Passing to components:
<!-- In outer components -->
<ul>
{#each content as { _id, ...data } (_id)}
<section {observer} {data}></section>
{/each}
</ul>
<!-- In Section.svelte -->
<section use:observer>
<div>...</div>
</section>
Listening only once:
<div use:observer={{ once: true }}></div>
<!-- or -->
<div use:observer data-io-once="true"></div>
Other demos:
createObserver accepts IO options, plus a callback on a single entry, and a visual toggle to show the root element's rootBound (give it a try! It's on the top menu bar of this page.)
interface Options {
root?: Element | Document | null;
rootMargin?: string;
threshold?: number | number[];
callback?: ({
entry: IntersectionObserverEntry
observer: IntersectionObserver
}) => void;
showRootBound?: boolean;
}
interface Returns {
observer: (node: HTMLElement) => void;
io: IntersectionObserver
}
⚠️ If you pass in a custom
callback, you'll have to create your own custom events. To retain the default behavior, importdefaultCallback& wrap around it:import { defaultCallback } from 'svelte-use-io'; const customCallback = ({ entry, observer }) => { doStuff(entry); defaultCallback({ entry, observer }); // send `on:intersecting`, `on:unintersecting` };
This code
<div use:observer={{ once: true }}>
...will observe the div only once. Note that if once is false and then set to true, div will be observed once again, once.
When an observed element is destroyed, it'll also be unobserved. The observer instance will be garbage-collected when it no longer observes anything and have no references (See this thread.)
If you'd like to disconnect all observers manually:
import { onDestroy } from 'svelte';
const { observer, io } = createObserver();
onDestroy(() => {
io.disconnect();
});
To prevent type error when adding custom event listeners on HTML elements, Add these to a definition file such as global.d.ts:
declare namespace svelte.JSX {
export interface HTMLAttributes<T> {
onintersecting?: () => void;
onunintersecting?: () => void;
}
}
Admittedly, the Intersection Observer API ('IO' from here on) is not difficult to use — but it is verbose and I have to look it up every time. This is a Svelte action that I've been copying from project to project & thought it's time to slab a few tests on it & publish as a package.
I think the IO API is a lot easier to handle as events on elements vs. the callback API:
<script>
import { createObserver } from 'svelte-use-io'
const { observer } = createObserver()
let intersecting = false
</script>
<div
use:observer
on:intersecting={() => (intersecting = true)}
on:unintersecting={() => (intersecting = false)}
>
<!-- ... -->
</div>
I'm also tempted to create a <Observer> component that may look something like this:
// ⚠️ This component doesn't exists
<Observer bind:intersecting>
<div>{/*...*/}</div>
</Observer>
...but then it'd need an extra html element. Should it be a div or a section? Is it ok to just spread ...$$props into it? Alternatively, I can do something like slot forwarding, but now that's just a different kind of boilerplate.
Feedback, thoughts, PRs are all welcomed.
showRootBound and scroll over this box