svelte-intersection-observer
Version:
Detects when an element enters or exits the viewport using the Intersection Observer API.
128 lines (104 loc) • 3.05 kB
JavaScript
import { fromAction } from "svelte/attachments";
export function intersect(node, options = {}) {
let {
root = null,
rootMargin = "0px",
threshold = 0,
once = false,
skip = false,
} = options;
let observer;
const createObserver = () => {
if (typeof IntersectionObserver === "undefined") return null;
return new IntersectionObserver(
(entries) => {
for (const entry of entries) {
node.dispatchEvent(new CustomEvent("observe", { detail: entry }));
if (entry.isIntersecting) {
node.dispatchEvent(new CustomEvent("intersect", { detail: entry }));
if (once) observer?.unobserve(node);
}
}
},
{ root, rootMargin, threshold },
);
};
observer = createObserver();
if (!skip) observer?.observe(node);
return {
update(newOptions = {}) {
once = newOptions.once ?? false;
const newSkip = newOptions.skip ?? false;
const configChanged =
(newOptions.root ?? null) !== root ||
(newOptions.rootMargin ?? "0px") !== rootMargin ||
JSON.stringify(newOptions.threshold ?? 0) !== JSON.stringify(threshold);
if (configChanged) {
root = newOptions.root ?? null;
rootMargin = newOptions.rootMargin ?? "0px";
threshold = newOptions.threshold ?? 0;
observer?.disconnect();
observer = createObserver();
if (!newSkip) observer?.observe(node);
} else if (newSkip !== skip) {
if (newSkip) observer?.unobserve(node);
else observer?.observe(node);
}
skip = newSkip;
},
destroy() {
observer?.disconnect();
},
};
}
export function intersectAttachment(getOptions = () => ({})) {
return fromAction(intersect, getOptions);
}
export function createIntersectionGroup(getSharedOptions = () => ({})) {
let observer;
const callbacks = new Map();
const handleEntries = (
entries,
) => {
for (const entry of entries) {
const target = entry.target;
const nodeOptions = callbacks.get(target);
nodeOptions?.onobserve?.(entry);
if (entry.isIntersecting) {
nodeOptions?.onintersect?.(
(
entry
),
);
if (nodeOptions?.once) observer?.unobserve(target);
}
}
};
function attach(nodeOptions = {}) {
return ( node) => {
if (!observer && typeof IntersectionObserver !== "undefined") {
const {
root = null,
rootMargin = "0px",
threshold = 0,
} = getSharedOptions();
observer = new IntersectionObserver(handleEntries, {
root,
rootMargin,
threshold,
});
}
callbacks.set(node, nodeOptions);
if (!nodeOptions.skip) observer?.observe(node);
return () => {
observer?.unobserve(node);
callbacks.delete(node);
if (callbacks.size === 0) {
observer?.disconnect();
observer = undefined;
}
};
};
}
return { attach };
}