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