Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,35 @@ As an alternative to binding the `intersecting` prop, you can listen to the `int
</IntersectionObserver>
```

### Multiple elements

For performance, use `MultipleIntersectionObserver` to observe multiple elements.

This avoids instantiating a new observer for every element.

```svelte
<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>
```

## API

### Props
Expand Down
134 changes: 134 additions & 0 deletions src/MultipleIntersectionObserver.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<script>
// @ts-check
/**
* Array of HTML Elements to observe.
* Use this for better performance when observing multiple elements.
* @type {HTMLElement[]}
*/
export let elements = [];

/**
* Set to `true` to unobserve the element
* after it intersects the viewport.
* @type {boolean}
*/
export let once = false;

/**
* Specify the containing element.
* Defaults to the browser viewport.
* @type {null | HTMLElement}
*/
export let root = null;

/** Margin offset of the containing element. */
export let rootMargin = "0px";

/**
* Percentage of element visibility to trigger an event.
* Value must be between 0 and 1.
*/
export let threshold = 0;

/**
* Map of element to its intersection state.
* @type {Map<HTMLElement, boolean>}
*/
export let elementIntersections = new Map();

/**
* Map of element to its latest entry.
* @type {Map<HTMLElement, IntersectionObserverEntry>}
*/
export let elementEntries = new Map();

/**
* `IntersectionObserver` instance.
* @type {null | IntersectionObserver}
*/
export let observer = null;

import { tick, createEventDispatcher, afterUpdate, onMount } from "svelte";

const dispatch = createEventDispatcher();

/** @type {null | string} */
let prevRootMargin = null;

/** @type {null | HTMLElement} */
let prevElement = null;

/** @type {HTMLElement[]} */
let prevElements = [];

const initialize = () => {
observer = new IntersectionObserver(
(entries) => {
entries.forEach((_entry) => {
const target = /** @type {HTMLElement} */ (_entry.target);

elementIntersections.set(target, _entry.isIntersecting);
elementEntries.set(target, _entry);

// Trigger reactivity.
elementIntersections = new Map(elementIntersections);
elementEntries = new Map(elementEntries);

dispatch("observe", { entry: _entry, target });

if (_entry.isIntersecting) {
dispatch("intersect", { entry: _entry, target });
if (once) observer?.unobserve(target);
}
});
},
{ root, rootMargin, threshold },
);
};

onMount(() => {
initialize();

return () => {
if (observer) {
observer.disconnect();
observer = null;
}
};
});

afterUpdate(async () => {
await tick();

if (elements.length > 0) {
const newElements = elements.filter((el) => !prevElements.includes(el));
newElements.forEach((el) => {
if (el) observer?.observe(el);
});

const removedElements = prevElements.filter(
(el) => !elements.includes(el),
);
removedElements.forEach((el) => {
if (el) observer?.unobserve(el);
});

prevElements = [...elements];
}

if (prevRootMargin && rootMargin !== prevRootMargin) {
observer?.disconnect();
prevElement = null;
prevElements = [];
initialize();

elements.forEach((el) => {
if (el) observer?.observe(el);
});
}

prevRootMargin = rootMargin;
});
</script>

<slot {observer} {elementIntersections} {elementEntries} />
82 changes: 82 additions & 0 deletions src/MultipleIntersectionObserver.svelte.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { SvelteComponentTyped } from "svelte";

export default class extends SvelteComponentTyped<
{
/**
* Array of HTML Elements to observe.
* Use this for better performance when observing multiple elements.
* @default []
*/
elements?: HTMLElement[];

/**
* Set to `true` to unobserve the element
* after it intersects the viewport.
* @default false
*/
once?: boolean;

/**
* Specify the containing element.
* Defaults to the browser viewport.
* @default null
*/
root?: null | HTMLElement;

/**
* Margin offset of the containing element.
* @default "0px"
*/
rootMargin?: string;

/**
* Percentage of element visibility to trigger an event.
* Value must be a number between 0 and 1, or an array of numbers between 0 and 1.
* @default 0
*/
threshold?: number | number[];

/**
* Map of element to its intersection state.
* @default new Map()
*/
elementIntersections?: Map<HTMLElement, boolean>;

/**
* Map of element to its latest entry.
* @default new Map()
*/
elementEntries?: Map<HTMLElement, IntersectionObserverEntry>;

/**
* `IntersectionObserver` instance.
* @default null
*/
observer?: null | IntersectionObserver;
},
{
/**
* Dispatched when an element is first observed
* and also whenever an intersection event occurs.
*/
observe: CustomEvent<{
entry: IntersectionObserverEntry;
target: HTMLElement;
}>;

/**
* Dispatched only when an element is intersecting the viewport.
*/
intersect: CustomEvent<{
entry: IntersectionObserverEntry & { isIntersecting: true };
target: HTMLElement;
}>;
},
{
default: {
observer: IntersectionObserver;
elementIntersections: Map<HTMLElement, boolean>;
elementEntries: Map<HTMLElement, IntersectionObserverEntry>;
};
}
> {}
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from "./IntersectionObserver.svelte";
export { default as MultipleIntersectionObserver } from "./MultipleIntersectionObserver.svelte";
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default } from "./IntersectionObserver.svelte";
export { default as MultipleIntersectionObserver } from "./MultipleIntersectionObserver.svelte";