svelte-intersection-observer 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/IntersectionObserver.svelte +27 -23
- package/MultipleIntersectionObserver.svelte +47 -40
- package/README.md +428 -233
- package/create-intersection-observer.svelte.d.ts +22 -0
- package/create-intersection-observer.svelte.js +36 -0
- package/index.d.ts +13 -2
- package/index.js +6 -1
- package/intersect.svelte.d.ts +90 -0
- package/intersect.svelte.js +76 -18
- package/package.json +2 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Attachment } from "svelte/attachments";
|
|
2
|
+
import type { IntersectActionOptions } from "./intersect.svelte.js";
|
|
3
|
+
|
|
4
|
+
export interface IntersectionObserverState {
|
|
5
|
+
/** `true` if the observed element is intersecting the viewport. */
|
|
6
|
+
readonly intersecting: boolean;
|
|
7
|
+
|
|
8
|
+
/** Observed element metadata. */
|
|
9
|
+
readonly entry: null | IntersectionObserverEntry;
|
|
10
|
+
|
|
11
|
+
/** Attachment to apply to the observed element via `{@attach}`. */
|
|
12
|
+
attach: Attachment<HTMLElement>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Rune-based composable that observes the attached element with the
|
|
17
|
+
* Intersection Observer API, without a wrapper component or `use:`/`{@attach}`
|
|
18
|
+
* directive of your own.
|
|
19
|
+
*/
|
|
20
|
+
export function createIntersectionObserver(
|
|
21
|
+
getOptions?: () => IntersectActionOptions,
|
|
22
|
+
): IntersectionObserverState;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { intersectAttachment } from "./intersect.svelte.js";
|
|
2
|
+
|
|
3
|
+
export function createIntersectionObserver(getOptions = () => ({})) {
|
|
4
|
+
let intersecting = $state(false);
|
|
5
|
+
let entry = $state( (null));
|
|
6
|
+
|
|
7
|
+
const attachment = intersectAttachment(getOptions);
|
|
8
|
+
|
|
9
|
+
function attach(node) {
|
|
10
|
+
const onObserve = (event) => {
|
|
11
|
+
const detail = (
|
|
12
|
+
event
|
|
13
|
+
).detail;
|
|
14
|
+
entry = detail;
|
|
15
|
+
intersecting = detail.isIntersecting;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
node.addEventListener("observe", onObserve);
|
|
19
|
+
const cleanup = attachment(node);
|
|
20
|
+
|
|
21
|
+
return () => {
|
|
22
|
+
node.removeEventListener("observe", onObserve);
|
|
23
|
+
if (typeof cleanup === "function") cleanup();
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
get intersecting() {
|
|
29
|
+
return intersecting;
|
|
30
|
+
},
|
|
31
|
+
get entry() {
|
|
32
|
+
return entry;
|
|
33
|
+
},
|
|
34
|
+
attach,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
export type { IntersectionObserverState } from "./create-intersection-observer.svelte.js";
|
|
2
|
+
export { createIntersectionObserver } from "./create-intersection-observer.svelte.js";
|
|
1
3
|
export { default } from "./IntersectionObserver.svelte";
|
|
2
|
-
export type {
|
|
3
|
-
|
|
4
|
+
export type {
|
|
5
|
+
IntersectActionOptions,
|
|
6
|
+
IntersectGroupNodeOptions,
|
|
7
|
+
IntersectGroupSharedOptions,
|
|
8
|
+
IntersectionGroup,
|
|
9
|
+
} from "./intersect.svelte.js";
|
|
10
|
+
export {
|
|
11
|
+
createIntersectionGroup,
|
|
12
|
+
intersect,
|
|
13
|
+
intersectAttachment,
|
|
14
|
+
} from "./intersect.svelte.js";
|
|
4
15
|
export { default as MultipleIntersectionObserver } from "./MultipleIntersectionObserver.svelte";
|
package/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export { createIntersectionObserver } from "./create-intersection-observer.svelte.js";
|
|
1
2
|
export { default } from "./IntersectionObserver.svelte";
|
|
2
|
-
export {
|
|
3
|
+
export {
|
|
4
|
+
createIntersectionGroup,
|
|
5
|
+
intersect,
|
|
6
|
+
intersectAttachment,
|
|
7
|
+
} from "./intersect.svelte.js";
|
|
3
8
|
export { default as MultipleIntersectionObserver } from "./MultipleIntersectionObserver.svelte";
|
package/intersect.svelte.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Action } from "svelte/action";
|
|
2
|
+
import type { Attachment } from "svelte/attachments";
|
|
2
3
|
|
|
3
4
|
export interface IntersectActionOptions {
|
|
4
5
|
/**
|
|
@@ -51,3 +52,92 @@ export const intersect: Action<
|
|
|
51
52
|
) => void;
|
|
52
53
|
}
|
|
53
54
|
>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Svelte attachment that observes the element with the Intersection Observer
|
|
58
|
+
* API. Equivalent to `intersect`, for use with `{@attach}` instead of `use:`.
|
|
59
|
+
*/
|
|
60
|
+
export function intersectAttachment(
|
|
61
|
+
getOptions?: () => IntersectActionOptions,
|
|
62
|
+
): Attachment<HTMLElement>;
|
|
63
|
+
|
|
64
|
+
declare module "svelte/elements" {
|
|
65
|
+
export interface HTMLAttributes<T> {
|
|
66
|
+
onobserve?: (event: CustomEvent<IntersectionObserverEntry>) => void;
|
|
67
|
+
onintersect?: (
|
|
68
|
+
event: CustomEvent<IntersectionObserverEntry & { isIntersecting: true }>,
|
|
69
|
+
) => void;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Options shared by every node in an `createIntersectionGroup` group. These
|
|
75
|
+
* configure the one underlying `IntersectionObserver` instance, so they
|
|
76
|
+
* can't vary per node.
|
|
77
|
+
*/
|
|
78
|
+
export interface IntersectGroupSharedOptions {
|
|
79
|
+
/**
|
|
80
|
+
* Specify the containing element.
|
|
81
|
+
* Defaults to the browser viewport.
|
|
82
|
+
* @default null
|
|
83
|
+
*/
|
|
84
|
+
root?: null | HTMLElement;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Margin offset of the containing element.
|
|
88
|
+
* @default "0px"
|
|
89
|
+
*/
|
|
90
|
+
rootMargin?: string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Percentage of element visibility to trigger an event.
|
|
94
|
+
* Value must be a number between 0 and 1, or an array of numbers between 0 and 1.
|
|
95
|
+
* @default 0
|
|
96
|
+
*/
|
|
97
|
+
threshold?: number | number[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Per-node options for a single element within an `createIntersectionGroup` group.
|
|
102
|
+
*/
|
|
103
|
+
export interface IntersectGroupNodeOptions {
|
|
104
|
+
/**
|
|
105
|
+
* Set to `true` to unobserve the element
|
|
106
|
+
* after it intersects the viewport.
|
|
107
|
+
* @default false
|
|
108
|
+
*/
|
|
109
|
+
once?: boolean;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Set to `true` to skip observing this element without
|
|
113
|
+
* affecting the rest of the group.
|
|
114
|
+
* @default false
|
|
115
|
+
*/
|
|
116
|
+
skip?: boolean;
|
|
117
|
+
|
|
118
|
+
/** Called when the element is first observed and also whenever an intersection change occurs. */
|
|
119
|
+
onobserve?: (entry: IntersectionObserverEntry) => void;
|
|
120
|
+
|
|
121
|
+
/** Called only when the element is intersecting the viewport. */
|
|
122
|
+
onintersect?: (
|
|
123
|
+
entry: IntersectionObserverEntry & { isIntersecting: true },
|
|
124
|
+
) => void;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface IntersectionGroup {
|
|
128
|
+
/**
|
|
129
|
+
* Returns an attachment for a single node in the group. Call once per
|
|
130
|
+
* element in a loop, e.g. `{@attach group.attach({ onobserve })}`.
|
|
131
|
+
*/
|
|
132
|
+
attach(options?: IntersectGroupNodeOptions): Attachment<HTMLElement>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Creates a group of elements backed by a single shared `IntersectionObserver`
|
|
137
|
+
* instance, for use with `{@attach}` inside an `#each` block. Brings
|
|
138
|
+
* `MultipleIntersectionObserver`'s single-observer performance benefit to the
|
|
139
|
+
* action/attachment API.
|
|
140
|
+
*/
|
|
141
|
+
export function createIntersectionGroup(
|
|
142
|
+
getSharedOptions?: () => IntersectGroupSharedOptions,
|
|
143
|
+
): IntersectionGroup;
|
package/intersect.svelte.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Dispatches `observe` (on every change) and `intersect` (on entering the
|
|
4
|
-
* viewport) `CustomEvent`s on `node` — listen with `onobserve`/`onintersect`.
|
|
5
|
-
* @param {HTMLElement} node
|
|
6
|
-
* @param {import("./intersect.svelte.d.ts").IntersectActionOptions} [options]
|
|
7
|
-
*/
|
|
1
|
+
import { fromAction } from "svelte/attachments";
|
|
2
|
+
|
|
8
3
|
export function intersect(node, options = {}) {
|
|
9
4
|
let {
|
|
10
5
|
root = null,
|
|
@@ -14,29 +9,30 @@ export function intersect(node, options = {}) {
|
|
|
14
9
|
skip = false,
|
|
15
10
|
} = options;
|
|
16
11
|
|
|
17
|
-
/** @type {IntersectionObserver} */
|
|
18
12
|
let observer;
|
|
19
13
|
|
|
20
|
-
const createObserver = () =>
|
|
21
|
-
|
|
14
|
+
const createObserver = () => {
|
|
15
|
+
if (typeof IntersectionObserver === "undefined") return null;
|
|
16
|
+
|
|
17
|
+
return new IntersectionObserver(
|
|
22
18
|
(entries) => {
|
|
23
19
|
for (const entry of entries) {
|
|
24
20
|
node.dispatchEvent(new CustomEvent("observe", { detail: entry }));
|
|
25
21
|
|
|
26
22
|
if (entry.isIntersecting) {
|
|
27
23
|
node.dispatchEvent(new CustomEvent("intersect", { detail: entry }));
|
|
28
|
-
if (once) observer
|
|
24
|
+
if (once) observer?.unobserve(node);
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
27
|
},
|
|
32
28
|
{ root, rootMargin, threshold },
|
|
33
29
|
);
|
|
30
|
+
};
|
|
34
31
|
|
|
35
32
|
observer = createObserver();
|
|
36
|
-
if (!skip) observer
|
|
33
|
+
if (!skip) observer?.observe(node);
|
|
37
34
|
|
|
38
35
|
return {
|
|
39
|
-
/** @param {import("./intersect.svelte.d.ts").IntersectActionOptions} [newOptions] */
|
|
40
36
|
update(newOptions = {}) {
|
|
41
37
|
once = newOptions.once ?? false;
|
|
42
38
|
|
|
@@ -52,18 +48,80 @@ export function intersect(node, options = {}) {
|
|
|
52
48
|
rootMargin = newOptions.rootMargin ?? "0px";
|
|
53
49
|
threshold = newOptions.threshold ?? 0;
|
|
54
50
|
|
|
55
|
-
observer
|
|
51
|
+
observer?.disconnect();
|
|
56
52
|
observer = createObserver();
|
|
57
|
-
if (!newSkip) observer
|
|
53
|
+
if (!newSkip) observer?.observe(node);
|
|
58
54
|
} else if (newSkip !== skip) {
|
|
59
|
-
if (newSkip) observer
|
|
60
|
-
else observer
|
|
55
|
+
if (newSkip) observer?.unobserve(node);
|
|
56
|
+
else observer?.observe(node);
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
skip = newSkip;
|
|
64
60
|
},
|
|
65
61
|
destroy() {
|
|
66
|
-
observer
|
|
62
|
+
observer?.disconnect();
|
|
67
63
|
},
|
|
68
64
|
};
|
|
69
65
|
}
|
|
66
|
+
|
|
67
|
+
export function intersectAttachment(getOptions = () => ({})) {
|
|
68
|
+
return fromAction(intersect, getOptions);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createIntersectionGroup(getSharedOptions = () => ({})) {
|
|
72
|
+
let observer;
|
|
73
|
+
|
|
74
|
+
const callbacks = new Map();
|
|
75
|
+
|
|
76
|
+
const handleEntries = (
|
|
77
|
+
entries,
|
|
78
|
+
) => {
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
const target = entry.target;
|
|
81
|
+
const nodeOptions = callbacks.get(target);
|
|
82
|
+
|
|
83
|
+
nodeOptions?.onobserve?.(entry);
|
|
84
|
+
|
|
85
|
+
if (entry.isIntersecting) {
|
|
86
|
+
nodeOptions?.onintersect?.(
|
|
87
|
+
(
|
|
88
|
+
entry
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
if (nodeOptions?.once) observer?.unobserve(target);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function attach(nodeOptions = {}) {
|
|
97
|
+
return ( node) => {
|
|
98
|
+
if (!observer && typeof IntersectionObserver !== "undefined") {
|
|
99
|
+
const {
|
|
100
|
+
root = null,
|
|
101
|
+
rootMargin = "0px",
|
|
102
|
+
threshold = 0,
|
|
103
|
+
} = getSharedOptions();
|
|
104
|
+
observer = new IntersectionObserver(handleEntries, {
|
|
105
|
+
root,
|
|
106
|
+
rootMargin,
|
|
107
|
+
threshold,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
callbacks.set(node, nodeOptions);
|
|
112
|
+
if (!nodeOptions.skip) observer?.observe(node);
|
|
113
|
+
|
|
114
|
+
return () => {
|
|
115
|
+
observer?.unobserve(node);
|
|
116
|
+
callbacks.delete(node);
|
|
117
|
+
|
|
118
|
+
if (callbacks.size === 0) {
|
|
119
|
+
observer?.disconnect();
|
|
120
|
+
observer = undefined;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { attach };
|
|
127
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-intersection-observer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Detects when an element enters or exits the viewport using the Intersection Observer API.",
|
|
6
6
|
"author": "Eric Liu (https://github.com/metonym)",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"svelte": "./index.js",
|