svelte-intersection-observer 1.1.2 → 1.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 +33 -7
- package/IntersectionObserver.svelte.d.ts +8 -0
- package/MultipleIntersectionObserver.svelte +47 -18
- package/MultipleIntersectionObserver.svelte.d.ts +8 -0
- package/README.md +154 -1
- package/index.d.ts +2 -0
- package/index.js +1 -0
- package/intersect.d.ts +53 -0
- package/intersect.js +69 -0
- package/package.json +2 -2
|
@@ -46,7 +46,15 @@
|
|
|
46
46
|
*/
|
|
47
47
|
export let observer = null;
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Set to `true` to pause observing without disconnecting the
|
|
51
|
+
* observer or losing `entry`/`intersecting` state. Set back to
|
|
52
|
+
* `false` to resume.
|
|
53
|
+
* @type {boolean}
|
|
54
|
+
*/
|
|
55
|
+
export let skip = false;
|
|
56
|
+
|
|
57
|
+
import { afterUpdate, createEventDispatcher, onMount, tick } from "svelte";
|
|
50
58
|
|
|
51
59
|
const dispatch = createEventDispatcher();
|
|
52
60
|
|
|
@@ -60,10 +68,12 @@
|
|
|
60
68
|
/** @type {null | HTMLElement} */
|
|
61
69
|
let prevElement = null;
|
|
62
70
|
|
|
71
|
+
let prevSkip = skip;
|
|
72
|
+
|
|
63
73
|
const initialize = () => {
|
|
64
74
|
observer = new IntersectionObserver(
|
|
65
75
|
(entries) => {
|
|
66
|
-
|
|
76
|
+
for (const _entry of entries) {
|
|
67
77
|
entry = _entry;
|
|
68
78
|
intersecting = _entry.isIntersecting;
|
|
69
79
|
|
|
@@ -74,7 +84,7 @@
|
|
|
74
84
|
|
|
75
85
|
if (element && once) observer?.unobserve(element);
|
|
76
86
|
}
|
|
77
|
-
}
|
|
87
|
+
}
|
|
78
88
|
},
|
|
79
89
|
{ root, rootMargin, threshold },
|
|
80
90
|
);
|
|
@@ -95,22 +105,38 @@
|
|
|
95
105
|
await tick();
|
|
96
106
|
|
|
97
107
|
if (element !== null && element !== prevElement) {
|
|
98
|
-
observer?.observe(element);
|
|
108
|
+
if (!skip) observer?.observe(element);
|
|
99
109
|
|
|
100
110
|
if (prevElement !== null) observer?.unobserve(prevElement);
|
|
101
111
|
prevElement = element;
|
|
102
112
|
}
|
|
103
113
|
|
|
104
|
-
if (
|
|
114
|
+
if (skip !== prevSkip && element !== null) {
|
|
115
|
+
if (skip) observer?.unobserve(element);
|
|
116
|
+
else observer?.observe(element);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
rootMargin !== prevRootMargin ||
|
|
121
|
+
threshold !== prevThreshold ||
|
|
122
|
+
root !== prevRoot
|
|
123
|
+
) {
|
|
105
124
|
observer?.disconnect();
|
|
106
|
-
prevElement = null;
|
|
107
125
|
initialize();
|
|
126
|
+
|
|
127
|
+
if (element !== null && !skip) observer?.observe(element);
|
|
128
|
+
prevElement = element;
|
|
108
129
|
}
|
|
109
130
|
|
|
110
131
|
prevRootMargin = rootMargin;
|
|
111
132
|
prevThreshold = threshold;
|
|
112
133
|
prevRoot = root;
|
|
134
|
+
prevSkip = skip;
|
|
113
135
|
});
|
|
114
136
|
</script>
|
|
115
137
|
|
|
116
|
-
<slot
|
|
138
|
+
<slot
|
|
139
|
+
{intersecting}
|
|
140
|
+
{entry}
|
|
141
|
+
{observer}
|
|
142
|
+
/>
|
|
@@ -53,6 +53,14 @@ export default class extends SvelteComponentTyped<
|
|
|
53
53
|
* @default null
|
|
54
54
|
*/
|
|
55
55
|
observer?: null | IntersectionObserver;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set to `true` to pause observing without disconnecting the
|
|
59
|
+
* observer or losing `entry`/`intersecting` state. Set back to
|
|
60
|
+
* `false` to resume.
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
skip?: boolean;
|
|
56
64
|
},
|
|
57
65
|
{
|
|
58
66
|
/**
|
|
@@ -47,7 +47,15 @@
|
|
|
47
47
|
*/
|
|
48
48
|
export let observer = null;
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Set to `true` to pause observing all elements without disconnecting
|
|
52
|
+
* the observer or losing `elementIntersections`/`elementEntries` state.
|
|
53
|
+
* Set back to `false` to resume.
|
|
54
|
+
* @type {boolean}
|
|
55
|
+
*/
|
|
56
|
+
export let skip = false;
|
|
57
|
+
|
|
58
|
+
import { afterUpdate, createEventDispatcher, onMount, tick } from "svelte";
|
|
51
59
|
|
|
52
60
|
const dispatch = createEventDispatcher();
|
|
53
61
|
|
|
@@ -61,26 +69,28 @@
|
|
|
61
69
|
/** @type {(HTMLElement | null)[]} */
|
|
62
70
|
let prevElements = [];
|
|
63
71
|
|
|
72
|
+
let prevSkip = skip;
|
|
73
|
+
|
|
64
74
|
const initialize = () => {
|
|
65
75
|
observer = new IntersectionObserver(
|
|
66
76
|
(entries) => {
|
|
67
|
-
|
|
77
|
+
for (const _entry of entries) {
|
|
68
78
|
const target = /** @type {HTMLElement} */ (_entry.target);
|
|
69
79
|
|
|
70
80
|
elementIntersections.set(target, _entry.isIntersecting);
|
|
71
81
|
elementEntries.set(target, _entry);
|
|
72
82
|
|
|
73
|
-
// Trigger reactivity.
|
|
74
|
-
elementIntersections = new Map(elementIntersections);
|
|
75
|
-
elementEntries = new Map(elementEntries);
|
|
76
|
-
|
|
77
83
|
dispatch("observe", { entry: _entry, target });
|
|
78
84
|
|
|
79
85
|
if (_entry.isIntersecting) {
|
|
80
86
|
dispatch("intersect", { entry: _entry, target });
|
|
81
87
|
if (once) observer?.unobserve(target);
|
|
82
88
|
}
|
|
83
|
-
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Trigger reactivity once per batch, not once per entry.
|
|
92
|
+
elementIntersections = new Map(elementIntersections);
|
|
93
|
+
elementEntries = new Map(elementEntries);
|
|
84
94
|
},
|
|
85
95
|
{ root, rootMargin, threshold },
|
|
86
96
|
);
|
|
@@ -104,36 +114,55 @@
|
|
|
104
114
|
const newElements = elements.filter(
|
|
105
115
|
(el) => el && !prevElements.includes(el),
|
|
106
116
|
);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
if (!skip) {
|
|
118
|
+
for (const el of newElements) {
|
|
119
|
+
if (el) observer?.observe(el);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
110
122
|
|
|
111
123
|
const removedElements = prevElements.filter(
|
|
112
124
|
(el) => el && !elements.includes(el),
|
|
113
125
|
);
|
|
114
|
-
|
|
126
|
+
for (const el of removedElements) {
|
|
115
127
|
if (el) observer?.unobserve(el);
|
|
116
|
-
}
|
|
128
|
+
}
|
|
117
129
|
|
|
118
130
|
prevElements = [...elements];
|
|
119
131
|
}
|
|
120
132
|
|
|
121
|
-
if (
|
|
133
|
+
if (skip !== prevSkip) {
|
|
134
|
+
for (const el of elements.filter((el) => el)) {
|
|
135
|
+
if (!el) continue;
|
|
136
|
+
if (skip) observer?.unobserve(el);
|
|
137
|
+
else observer?.observe(el);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
rootMargin !== prevRootMargin ||
|
|
143
|
+
threshold !== prevThreshold ||
|
|
144
|
+
root !== prevRoot
|
|
145
|
+
) {
|
|
122
146
|
observer?.disconnect();
|
|
123
147
|
prevElements = [];
|
|
124
148
|
initialize();
|
|
125
149
|
|
|
126
|
-
|
|
127
|
-
.filter((el) => el)
|
|
128
|
-
.forEach((el) => {
|
|
150
|
+
if (!skip) {
|
|
151
|
+
for (const el of elements.filter((el) => el)) {
|
|
129
152
|
if (el) observer?.observe(el);
|
|
130
|
-
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
131
155
|
}
|
|
132
156
|
|
|
133
157
|
prevRootMargin = rootMargin;
|
|
134
158
|
prevThreshold = threshold;
|
|
135
159
|
prevRoot = root;
|
|
160
|
+
prevSkip = skip;
|
|
136
161
|
});
|
|
137
162
|
</script>
|
|
138
163
|
|
|
139
|
-
<slot
|
|
164
|
+
<slot
|
|
165
|
+
{observer}
|
|
166
|
+
{elementIntersections}
|
|
167
|
+
{elementEntries}
|
|
168
|
+
/>
|
|
@@ -53,6 +53,14 @@ export default class extends SvelteComponentTyped<
|
|
|
53
53
|
* @default null
|
|
54
54
|
*/
|
|
55
55
|
observer?: null | IntersectionObserver;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set to `true` to pause observing all elements without disconnecting
|
|
59
|
+
* the observer or losing `elementIntersections`/`elementEntries` state.
|
|
60
|
+
* Set back to `false` to resume.
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
skip?: boolean;
|
|
56
64
|
},
|
|
57
65
|
{
|
|
58
66
|
/**
|
package/README.md
CHANGED
|
@@ -77,6 +77,27 @@ Set `once` to `true` for the intersection event to occur only once. The `element
|
|
|
77
77
|
</IntersectionObserver>
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
### Pausing with `skip`
|
|
81
|
+
|
|
82
|
+
Set `skip` to `true` to unobserve without disconnecting the underlying observer or losing `entry`/`intersecting` state — useful for pausing tracking on an off-screen carousel panel or a closed modal. Set `skip` back to `false` to resume; unlike `once`, this can be toggled back and forth. `MultipleIntersectionObserver` and the `intersect` action support the same `skip` option.
|
|
83
|
+
|
|
84
|
+
```svelte no-eval
|
|
85
|
+
<script>
|
|
86
|
+
import IntersectionObserver from "svelte-intersection-observer";
|
|
87
|
+
|
|
88
|
+
let elementSkip;
|
|
89
|
+
let paused = false;
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<button on:click={() => (paused = !paused)}>
|
|
93
|
+
{paused ? "Resume" : "Pause"}
|
|
94
|
+
</button>
|
|
95
|
+
|
|
96
|
+
<IntersectionObserver element={elementSkip} skip={paused} let:intersecting>
|
|
97
|
+
<div bind:this={elementSkip}>{intersecting ? "In view" : "Not in view"}</div>
|
|
98
|
+
</IntersectionObserver>
|
|
99
|
+
```
|
|
100
|
+
|
|
80
101
|
### `let:intersecting`
|
|
81
102
|
|
|
82
103
|
An alternative to binding to the `intersecting` prop is to use the `let:` directive.
|
|
@@ -91,7 +112,7 @@ In the following example, the "Hello world" element will fade in when its contai
|
|
|
91
112
|
let node;
|
|
92
113
|
</script>
|
|
93
114
|
|
|
94
|
-
<header
|
|
115
|
+
<header></header>
|
|
95
116
|
|
|
96
117
|
<IntersectionObserver element={node} let:intersecting>
|
|
97
118
|
<div bind:this={node}>
|
|
@@ -136,6 +157,65 @@ As an alternative to binding the `intersecting` prop, you can listen to the `int
|
|
|
136
157
|
</IntersectionObserver>
|
|
137
158
|
```
|
|
138
159
|
|
|
160
|
+
### Detecting scroll to end
|
|
161
|
+
|
|
162
|
+
To detect when a user has scrolled to the end of a scrollable container, place a sentinel element after the content and set `root` to the container. `intersecting` becomes `true` once the sentinel scrolls into view.
|
|
163
|
+
|
|
164
|
+
**Note**: `root` must be the scrollable element itself (i.e. it has its own `overflow`/fixed `height`), not just an ancestor of one. If `root` merely sits inside a scrollable ancestor, the sentinel scrolls along with `root` and never changes position relative to it, so it reports as permanently intersecting.
|
|
165
|
+
|
|
166
|
+
```svelte
|
|
167
|
+
<script>
|
|
168
|
+
import IntersectionObserver from "svelte-intersection-observer";
|
|
169
|
+
|
|
170
|
+
let container;
|
|
171
|
+
let sentinel;
|
|
172
|
+
let reachedEnd;
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<header class:intersecting={reachedEnd}>
|
|
176
|
+
{reachedEnd ? "You've reached the end" : "Keep scrolling..."}
|
|
177
|
+
</header>
|
|
178
|
+
|
|
179
|
+
<div bind:this={container} style="height: 200px; overflow-y: auto;">
|
|
180
|
+
{#each Array.from({ length: 20 }) as _, i}
|
|
181
|
+
<p>Paragraph {i + 1}</p>
|
|
182
|
+
{/each}
|
|
183
|
+
|
|
184
|
+
<IntersectionObserver
|
|
185
|
+
element={sentinel}
|
|
186
|
+
root={container}
|
|
187
|
+
bind:intersecting={reachedEnd}
|
|
188
|
+
>
|
|
189
|
+
<div bind:this={sentinel} style="height: 1px;"></div>
|
|
190
|
+
</IntersectionObserver>
|
|
191
|
+
</div>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### `use:intersect` action
|
|
195
|
+
|
|
196
|
+
As an alternative to the `IntersectionObserver` component, use the `intersect` action to observe an element directly with `use:`, without a `bind:this` reference or wrapper markup. Listen for `on:observe`/`on:intersect` on the observed element itself.
|
|
197
|
+
|
|
198
|
+
```svelte
|
|
199
|
+
<script>
|
|
200
|
+
import { intersect } from "svelte-intersection-observer";
|
|
201
|
+
|
|
202
|
+
let actionIntersecting = false;
|
|
203
|
+
</script>
|
|
204
|
+
|
|
205
|
+
<header class:intersecting={actionIntersecting}>
|
|
206
|
+
{actionIntersecting ? "Element is in view" : "Element is not in view"}
|
|
207
|
+
</header>
|
|
208
|
+
|
|
209
|
+
<div
|
|
210
|
+
use:intersect={{ once: true }}
|
|
211
|
+
on:observe={(e) => (actionIntersecting = e.detail.isIntersecting)}
|
|
212
|
+
>
|
|
213
|
+
Hello world
|
|
214
|
+
</div>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Options passed to `use:intersect` are reactive — updating `root`, `rootMargin`, or `threshold` re-initializes the underlying observer. Updating `skip` toggles observing on the existing observer without re-initializing it.
|
|
218
|
+
|
|
139
219
|
### Multiple elements
|
|
140
220
|
|
|
141
221
|
For performance, use `MultipleIntersectionObserver` to observe multiple elements.
|
|
@@ -167,6 +247,56 @@ This avoids instantiating a new observer for every element.
|
|
|
167
247
|
</MultipleIntersectionObserver>
|
|
168
248
|
```
|
|
169
249
|
|
|
250
|
+
### Using with `#each`
|
|
251
|
+
|
|
252
|
+
`MultipleIntersectionObserver` also handles a dynamic, `#each`-driven list — give every item its own slot in an array/object instead of one shared variable.
|
|
253
|
+
|
|
254
|
+
```svelte
|
|
255
|
+
<script>
|
|
256
|
+
import { MultipleIntersectionObserver } from "svelte-intersection-observer";
|
|
257
|
+
|
|
258
|
+
let items = [
|
|
259
|
+
{ id: 1, text: "Item 1" },
|
|
260
|
+
{ id: 2, text: "Item 2" },
|
|
261
|
+
{ id: 3, text: "Item 3" },
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
let refs = [];
|
|
265
|
+
let itemsContainer;
|
|
266
|
+
|
|
267
|
+
$: itemElements = refs;
|
|
268
|
+
</script>
|
|
269
|
+
|
|
270
|
+
<MultipleIntersectionObserver
|
|
271
|
+
elements={itemElements}
|
|
272
|
+
root={itemsContainer}
|
|
273
|
+
let:elementIntersections
|
|
274
|
+
>
|
|
275
|
+
<header>
|
|
276
|
+
{#each items as item, i (item.id)}
|
|
277
|
+
<div class:intersecting={elementIntersections.get(refs[i])}>
|
|
278
|
+
{item.text}: {elementIntersections.get(refs[i]) ? "✓" : "✗"}
|
|
279
|
+
</div>
|
|
280
|
+
{/each}
|
|
281
|
+
</header>
|
|
282
|
+
|
|
283
|
+
<div bind:this={itemsContainer} style="height: 150px; overflow-y: auto;">
|
|
284
|
+
{#each items as item, i (item.id)}
|
|
285
|
+
<div
|
|
286
|
+
bind:this={refs[i]}
|
|
287
|
+
style="height: 100px; display: flex; align-items: center;"
|
|
288
|
+
>
|
|
289
|
+
{item.text}
|
|
290
|
+
</div>
|
|
291
|
+
{/each}
|
|
292
|
+
</div>
|
|
293
|
+
</MultipleIntersectionObserver>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
As with the scroll-to-end example, `root` must be an element that scrolls on its own — here, `itemsContainer` has an explicit `height` and `overflow-y: auto`.
|
|
297
|
+
|
|
298
|
+
**Avoid** using the single-element `IntersectionObserver` component inside an `#each` block with one variable shared across iterations (e.g. `let node;` declared outside the loop and bound via `bind:this={node}` inside it). Every iteration overwrites the same `node`, so each observer instance keeps re-observing a moving target, which can produce an infinite update loop. Use `MultipleIntersectionObserver` with a per-item ref, as shown above, instead.
|
|
299
|
+
|
|
170
300
|
## API
|
|
171
301
|
|
|
172
302
|
### IntersectionObserver
|
|
@@ -183,6 +313,9 @@ This avoids instantiating a new observer for every element.
|
|
|
183
313
|
| threshold | Percentage of element visibility to trigger an event | `number` between 0 and 1, or an array of `number`s between 0 and 1 | `0` |
|
|
184
314
|
| entry | Observed element metadata | `null` or [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) | `null` |
|
|
185
315
|
| observer | `IntersectionObserver` instance | `null` or [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) | `null` |
|
|
316
|
+
| skip | Pause observing without losing `entry`/`intersecting` state | `boolean` | `false` |
|
|
317
|
+
|
|
318
|
+
**Note**: the observed `element` must render with a non-zero width and height for `threshold` values greater than `0` to have any effect — this is a constraint of the underlying [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API), not something this component controls.
|
|
186
319
|
|
|
187
320
|
#### Dispatched events
|
|
188
321
|
|
|
@@ -213,6 +346,7 @@ The `e.detail` dispatched by the `observe` and `intersect` events is an [`Inters
|
|
|
213
346
|
| elementIntersections | Map of each element to its intersection state | `Map<HTMLElement \| null, boolean>` | `new Map()` |
|
|
214
347
|
| elementEntries | Map of each element to its latest entry | `Map<HTMLElement \| null,` [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)`>` | `new Map()` |
|
|
215
348
|
| observer | `IntersectionObserver` instance | `null` or [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) | `null` |
|
|
349
|
+
| skip | Pause observing all elements without losing state | `boolean` | `false` |
|
|
216
350
|
|
|
217
351
|
#### Dispatched events
|
|
218
352
|
|
|
@@ -236,6 +370,25 @@ The `e.detail` for both events includes:
|
|
|
236
370
|
| elementIntersections | `Map<HTMLElement \| null, boolean>` |
|
|
237
371
|
| elementEntries | `Map<HTMLElement \| null,` [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)`>` |
|
|
238
372
|
|
|
373
|
+
### `intersect` action
|
|
374
|
+
|
|
375
|
+
#### Options
|
|
376
|
+
|
|
377
|
+
| Name | Description | Type | Default value |
|
|
378
|
+
| :--------- | :------------------------------------------------------------ | :------------------------------------------------------------------- | :------------- |
|
|
379
|
+
| root | Containing element | `null` or `HTMLElement` | `null` |
|
|
380
|
+
| rootMargin | Margin offset of the containing element | `string` | `"0px"` |
|
|
381
|
+
| threshold | Percentage of element visibility to trigger an event | `number` between 0 and 1, or an array of `number`s between 0 and 1 | `0` |
|
|
382
|
+
| once | Unobserve the element after the first intersection event | `boolean` | `false` |
|
|
383
|
+
| skip | Pause observing without disconnecting the observer | `boolean` | `false` |
|
|
384
|
+
|
|
385
|
+
#### Dispatched events
|
|
386
|
+
|
|
387
|
+
- **on:observe**: fired on the element when it is first observed or whenever an intersection change occurs
|
|
388
|
+
- **on:intersect**: fired on the element when it is intersecting the viewport
|
|
389
|
+
|
|
390
|
+
The `e.detail` dispatched by the `observe` and `intersect` events is an [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) interface.
|
|
391
|
+
|
|
239
392
|
### `IntersectionObserverEntry` interface
|
|
240
393
|
|
|
241
394
|
Note that all properties in [IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) are read-only.
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/intersect.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Action } from "svelte/action";
|
|
2
|
+
|
|
3
|
+
export interface IntersectActionOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Specify the containing element.
|
|
6
|
+
* Defaults to the browser viewport.
|
|
7
|
+
* @default null
|
|
8
|
+
*/
|
|
9
|
+
root?: null | HTMLElement;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Margin offset of the containing element.
|
|
13
|
+
* @default "0px"
|
|
14
|
+
*/
|
|
15
|
+
rootMargin?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Percentage of element visibility to trigger an event.
|
|
19
|
+
* Value must be a number between 0 and 1, or an array of numbers between 0 and 1.
|
|
20
|
+
* @default 0
|
|
21
|
+
*/
|
|
22
|
+
threshold?: number | number[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Set to `true` to unobserve the element
|
|
26
|
+
* after it intersects the viewport.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
once?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Set to `true` to pause observing without disconnecting the
|
|
33
|
+
* observer. Set back to `false` to resume.
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
skip?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Svelte action that observes the element with the Intersection Observer API.
|
|
41
|
+
* Dispatches `observe` (on every change) and `intersect` (on entering the
|
|
42
|
+
* viewport) `CustomEvent`s on the element — listen with `on:observe`/`on:intersect`.
|
|
43
|
+
*/
|
|
44
|
+
export const intersect: Action<
|
|
45
|
+
HTMLElement,
|
|
46
|
+
IntersectActionOptions | undefined,
|
|
47
|
+
{
|
|
48
|
+
"on:observe"?: (event: CustomEvent<IntersectionObserverEntry>) => void;
|
|
49
|
+
"on:intersect"?: (
|
|
50
|
+
event: CustomEvent<IntersectionObserverEntry & { isIntersecting: true }>,
|
|
51
|
+
) => void;
|
|
52
|
+
}
|
|
53
|
+
>;
|
package/intersect.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action that observes `node` with the Intersection Observer API.
|
|
3
|
+
* Dispatches `observe` (on every change) and `intersect` (on entering the
|
|
4
|
+
* viewport) `CustomEvent`s on `node` — listen with `on:observe`/`on:intersect`.
|
|
5
|
+
* @param {HTMLElement} node
|
|
6
|
+
* @param {import("./intersect.d.ts").IntersectActionOptions} [options]
|
|
7
|
+
*/
|
|
8
|
+
export function intersect(node, options = {}) {
|
|
9
|
+
let {
|
|
10
|
+
root = null,
|
|
11
|
+
rootMargin = "0px",
|
|
12
|
+
threshold = 0,
|
|
13
|
+
once = false,
|
|
14
|
+
skip = false,
|
|
15
|
+
} = options;
|
|
16
|
+
|
|
17
|
+
/** @type {IntersectionObserver} */
|
|
18
|
+
let observer;
|
|
19
|
+
|
|
20
|
+
const createObserver = () =>
|
|
21
|
+
new IntersectionObserver(
|
|
22
|
+
(entries) => {
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
node.dispatchEvent(new CustomEvent("observe", { detail: entry }));
|
|
25
|
+
|
|
26
|
+
if (entry.isIntersecting) {
|
|
27
|
+
node.dispatchEvent(new CustomEvent("intersect", { detail: entry }));
|
|
28
|
+
if (once) observer.unobserve(node);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{ root, rootMargin, threshold },
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
observer = createObserver();
|
|
36
|
+
if (!skip) observer.observe(node);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
/** @param {import("./intersect.d.ts").IntersectActionOptions} [newOptions] */
|
|
40
|
+
update(newOptions = {}) {
|
|
41
|
+
once = newOptions.once ?? false;
|
|
42
|
+
|
|
43
|
+
const newSkip = newOptions.skip ?? false;
|
|
44
|
+
|
|
45
|
+
const configChanged =
|
|
46
|
+
(newOptions.root ?? null) !== root ||
|
|
47
|
+
(newOptions.rootMargin ?? "0px") !== rootMargin ||
|
|
48
|
+
JSON.stringify(newOptions.threshold ?? 0) !== JSON.stringify(threshold);
|
|
49
|
+
|
|
50
|
+
if (configChanged) {
|
|
51
|
+
root = newOptions.root ?? null;
|
|
52
|
+
rootMargin = newOptions.rootMargin ?? "0px";
|
|
53
|
+
threshold = newOptions.threshold ?? 0;
|
|
54
|
+
|
|
55
|
+
observer.disconnect();
|
|
56
|
+
observer = createObserver();
|
|
57
|
+
if (!newSkip) observer.observe(node);
|
|
58
|
+
} else if (newSkip !== skip) {
|
|
59
|
+
if (newSkip) observer.unobserve(node);
|
|
60
|
+
else observer.observe(node);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
skip = newSkip;
|
|
64
|
+
},
|
|
65
|
+
destroy() {
|
|
66
|
+
observer.disconnect();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-intersection-observer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Detect if an element is in the viewport using the Intersection Observer API",
|
|
6
6
|
"author": "Eric Liu (https://github.com/metonym)",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/metonym/svelte-intersection-observer.git"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.
|
|
13
|
+
"homepage": "https://metonym.github.io/svelte-intersection-observer/",
|
|
14
14
|
"bugs": "https://github.com/metonym/svelte-intersection-observer/issues",
|
|
15
15
|
"keywords": [
|
|
16
16
|
"svelte",
|