svelte-intersection-observer 1.2.0 → 2.1.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 +53 -100
- package/IntersectionObserver.svelte.d.ts +85 -77
- package/MultipleIntersectionObserver.svelte +61 -108
- package/MultipleIntersectionObserver.svelte.d.ts +88 -80
- package/README.md +212 -133
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/{intersect.d.ts → intersect.svelte.d.ts} +21 -3
- package/{intersect.js → intersect.svelte.js} +15 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
# svelte-intersection-observer
|
|
2
2
|
|
|
3
|
+
<!-- HIDE_START -->
|
|
3
4
|
[![NPM][npm]][npm-url]
|
|
5
|
+
<!-- HIDE_END -->
|
|
4
6
|
|
|
5
7
|
> Detect if an element is in the viewport using the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
Use it to lazy-load images, trigger scroll animations, implement infinite scroll, autoplay video when visible, track ad or analytics impressions, or detect when a user has scrolled to the end of a list.
|
|
10
|
+
|
|
11
|
+
This zero-dependency library offers several ways to observe elements:
|
|
12
|
+
|
|
13
|
+
- `IntersectionObserver`: component for observing a single element
|
|
14
|
+
- `MultipleIntersectionObserver`: component for observing multiple elements (shared observer for better performance)
|
|
15
|
+
- `intersect`: action for observing an element directly with `use:`
|
|
16
|
+
- `intersectAttachment`: attachment for observing an element directly with `{@attach}` (Svelte 5.29+)
|
|
8
17
|
|
|
9
18
|
Try it in the [Svelte REPL](https://svelte.dev/repl/8cd2327a580c4f429c71f7df999bd51d).
|
|
10
19
|
|
|
20
|
+
## Compatibility
|
|
21
|
+
|
|
22
|
+
| Package version | Svelte version | Notes |
|
|
23
|
+
| :--------------- | :----------------- | :----------------------------------------- |
|
|
24
|
+
| 1.x | 3, 4, 5 (non-runes) | Uses `export let`, slots, and `on:` events |
|
|
25
|
+
| 2.x | 5 (runes mode only) | Uses `$props()`, snippets, and callback props |
|
|
26
|
+
|
|
11
27
|
<!-- TOC -->
|
|
12
28
|
|
|
13
29
|
## Installation
|
|
@@ -39,8 +55,8 @@ Then, simply bind to the reactive `intersecting` prop to determine if the elemen
|
|
|
39
55
|
<script>
|
|
40
56
|
import IntersectionObserver from "svelte-intersection-observer";
|
|
41
57
|
|
|
42
|
-
let element;
|
|
43
|
-
let intersecting;
|
|
58
|
+
let element = $state();
|
|
59
|
+
let intersecting = $state(false);
|
|
44
60
|
</script>
|
|
45
61
|
|
|
46
62
|
<header class:intersecting>
|
|
@@ -52,6 +68,33 @@ Then, simply bind to the reactive `intersecting` prop to determine if the elemen
|
|
|
52
68
|
</IntersectionObserver>
|
|
53
69
|
```
|
|
54
70
|
|
|
71
|
+
### `children` snippet
|
|
72
|
+
|
|
73
|
+
An alternative to binding to the `intersecting` prop is to use the `children` snippet, which receives `intersecting`, `entry`, and `observer`.
|
|
74
|
+
|
|
75
|
+
In the following example, the "Hello world" element fades in when its containing element intersects the viewport.
|
|
76
|
+
|
|
77
|
+
```svelte
|
|
78
|
+
<script>
|
|
79
|
+
import IntersectionObserver from "svelte-intersection-observer";
|
|
80
|
+
import { fade } from "svelte/transition";
|
|
81
|
+
|
|
82
|
+
let node = $state();
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<header></header>
|
|
86
|
+
|
|
87
|
+
<IntersectionObserver element={node}>
|
|
88
|
+
{#snippet children({ intersecting })}
|
|
89
|
+
<div bind:this={node}>
|
|
90
|
+
{#if intersecting}
|
|
91
|
+
<div transition:fade={{ delay: 200 }}>Hello world</div>
|
|
92
|
+
{/if}
|
|
93
|
+
</div>
|
|
94
|
+
{/snippet}
|
|
95
|
+
</IntersectionObserver>
|
|
96
|
+
```
|
|
97
|
+
|
|
55
98
|
### Once
|
|
56
99
|
|
|
57
100
|
Set `once` to `true` for the intersection event to occur only once. The `element` will be unobserved after the first intersection event occurs.
|
|
@@ -60,8 +103,8 @@ Set `once` to `true` for the intersection event to occur only once. The `element
|
|
|
60
103
|
<script>
|
|
61
104
|
import IntersectionObserver from "svelte-intersection-observer";
|
|
62
105
|
|
|
63
|
-
let elementOnce;
|
|
64
|
-
let intersectOnce;
|
|
106
|
+
let elementOnce = $state();
|
|
107
|
+
let intersectOnce = $state(false);
|
|
65
108
|
</script>
|
|
66
109
|
|
|
67
110
|
<header class:intersecting={intersectOnce}>
|
|
@@ -79,78 +122,55 @@ Set `once` to `true` for the intersection event to occur only once. The `element
|
|
|
79
122
|
|
|
80
123
|
### Pausing with `skip`
|
|
81
124
|
|
|
82
|
-
Set `skip` to `true` to unobserve without disconnecting the underlying observer or losing `entry`/`intersecting` state
|
|
125
|
+
Set `skip` to `true` to unobserve without disconnecting the underlying observer or losing `entry`/`intersecting` state. This is 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
126
|
|
|
84
127
|
```svelte no-eval
|
|
85
128
|
<script>
|
|
86
129
|
import IntersectionObserver from "svelte-intersection-observer";
|
|
87
130
|
|
|
88
|
-
let elementSkip;
|
|
89
|
-
let paused = false;
|
|
131
|
+
let elementSkip = $state();
|
|
132
|
+
let paused = $state(false);
|
|
90
133
|
</script>
|
|
91
134
|
|
|
92
|
-
<button
|
|
135
|
+
<button onclick={() => (paused = !paused)}>
|
|
93
136
|
{paused ? "Resume" : "Pause"}
|
|
94
137
|
</button>
|
|
95
138
|
|
|
96
|
-
<IntersectionObserver element={elementSkip} skip={paused}
|
|
97
|
-
|
|
139
|
+
<IntersectionObserver element={elementSkip} skip={paused}>
|
|
140
|
+
{#snippet children({ intersecting })}
|
|
141
|
+
<div bind:this={elementSkip}>{intersecting ? "In view" : "Not in view"}</div>
|
|
142
|
+
{/snippet}
|
|
98
143
|
</IntersectionObserver>
|
|
99
144
|
```
|
|
100
145
|
|
|
101
|
-
### `
|
|
102
|
-
|
|
103
|
-
An alternative to binding to the `intersecting` prop is to use the `let:` directive.
|
|
146
|
+
### `onobserve` callback prop
|
|
104
147
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
```svelte
|
|
108
|
-
<script>
|
|
109
|
-
import IntersectionObserver from "svelte-intersection-observer";
|
|
110
|
-
import { fade } from "svelte/transition";
|
|
111
|
-
|
|
112
|
-
let node;
|
|
113
|
-
</script>
|
|
114
|
-
|
|
115
|
-
<header></header>
|
|
116
|
-
|
|
117
|
-
<IntersectionObserver element={node} let:intersecting>
|
|
118
|
-
<div bind:this={node}>
|
|
119
|
-
{#if intersecting}
|
|
120
|
-
<div transition:fade={{ delay: 200 }}>Hello world</div>
|
|
121
|
-
{/if}
|
|
122
|
-
</div>
|
|
123
|
-
</IntersectionObserver>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### `on:observe` event
|
|
127
|
-
|
|
128
|
-
The `observe` event is dispatched when the element is first observed and also whenever an intersection event occurs.
|
|
148
|
+
`onobserve` is called when the element is first observed and also whenever an intersection event occurs.
|
|
129
149
|
|
|
130
150
|
```svelte no-eval
|
|
131
151
|
<IntersectionObserver
|
|
132
152
|
{element}
|
|
133
|
-
|
|
134
|
-
console.log(
|
|
135
|
-
console.log(
|
|
153
|
+
onobserve={(entry) => {
|
|
154
|
+
console.log(entry); // IntersectionObserverEntry
|
|
155
|
+
console.log(entry.isIntersecting); // true | false
|
|
136
156
|
}}
|
|
137
157
|
>
|
|
138
158
|
<div bind:this={element}>Hello world</div>
|
|
139
159
|
</IntersectionObserver>
|
|
140
160
|
```
|
|
141
161
|
|
|
142
|
-
### `
|
|
162
|
+
### `onintersect` callback prop
|
|
143
163
|
|
|
144
|
-
As an alternative to binding the `intersecting` prop, you can
|
|
164
|
+
As an alternative to binding the `intersecting` prop, you can pass an `onintersect` callback that is called if the observed element is intersecting the viewport.
|
|
145
165
|
|
|
146
|
-
**Note**: Compared to `
|
|
166
|
+
**Note**: Compared to `onobserve`, `onintersect` is called only when the element is _intersecting the viewport_. In other words, `entry.isIntersecting` will only be `true`.
|
|
147
167
|
|
|
148
168
|
```svelte no-eval
|
|
149
169
|
<IntersectionObserver
|
|
150
170
|
{element}
|
|
151
|
-
|
|
152
|
-
console.log(
|
|
153
|
-
console.log(
|
|
171
|
+
onintersect={(entry) => {
|
|
172
|
+
console.log(entry); // IntersectionObserverEntry
|
|
173
|
+
console.log(entry.isIntersecting); // true
|
|
154
174
|
}}
|
|
155
175
|
>
|
|
156
176
|
<div bind:this={element}>Hello world</div>
|
|
@@ -167,9 +187,9 @@ To detect when a user has scrolled to the end of a scrollable container, place a
|
|
|
167
187
|
<script>
|
|
168
188
|
import IntersectionObserver from "svelte-intersection-observer";
|
|
169
189
|
|
|
170
|
-
let container;
|
|
171
|
-
let sentinel;
|
|
172
|
-
let reachedEnd;
|
|
190
|
+
let container = $state();
|
|
191
|
+
let sentinel = $state();
|
|
192
|
+
let reachedEnd = $state(false);
|
|
173
193
|
</script>
|
|
174
194
|
|
|
175
195
|
<header class:intersecting={reachedEnd}>
|
|
@@ -191,15 +211,49 @@ To detect when a user has scrolled to the end of a scrollable container, place a
|
|
|
191
211
|
</div>
|
|
192
212
|
```
|
|
193
213
|
|
|
214
|
+
### Scrolling to a conditionally revealed element
|
|
215
|
+
|
|
216
|
+
Keep the `bind:this` element outside the `{#if intersecting}` block, and only gate the animated content inside it. The bound element then stays in the DOM, so you can call `scrollIntoView()` on it whenever you want, whether or not its reveal animation has played yet.
|
|
217
|
+
|
|
218
|
+
```svelte
|
|
219
|
+
<script>
|
|
220
|
+
import IntersectionObserver from "svelte-intersection-observer";
|
|
221
|
+
import { fly } from "svelte/transition";
|
|
222
|
+
|
|
223
|
+
let revealNode = $state();
|
|
224
|
+
let revealIntersecting = $state(false);
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<header class:intersecting={revealIntersecting}>
|
|
228
|
+
<button
|
|
229
|
+
onclick={() => {
|
|
230
|
+
revealNode.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
231
|
+
}}
|
|
232
|
+
>
|
|
233
|
+
Scroll to section
|
|
234
|
+
</button>
|
|
235
|
+
</header>
|
|
236
|
+
|
|
237
|
+
<IntersectionObserver element={revealNode} once bind:intersecting={revealIntersecting}>
|
|
238
|
+
<div bind:this={revealNode}>
|
|
239
|
+
{#if revealIntersecting}
|
|
240
|
+
<section transition:fly={{ y: 50, delay: 200, duration: 300 }}>
|
|
241
|
+
Hello world
|
|
242
|
+
</section>
|
|
243
|
+
{/if}
|
|
244
|
+
</div>
|
|
245
|
+
</IntersectionObserver>
|
|
246
|
+
```
|
|
247
|
+
|
|
194
248
|
### `use:intersect` action
|
|
195
249
|
|
|
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 `
|
|
250
|
+
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 `onobserve`/`onintersect` on the observed element itself.
|
|
197
251
|
|
|
198
252
|
```svelte
|
|
199
253
|
<script>
|
|
200
254
|
import { intersect } from "svelte-intersection-observer";
|
|
201
255
|
|
|
202
|
-
let actionIntersecting = false;
|
|
256
|
+
let actionIntersecting = $state(false);
|
|
203
257
|
</script>
|
|
204
258
|
|
|
205
259
|
<header class:intersecting={actionIntersecting}>
|
|
@@ -208,92 +262,123 @@ As an alternative to the `IntersectionObserver` component, use the `intersect` a
|
|
|
208
262
|
|
|
209
263
|
<div
|
|
210
264
|
use:intersect={{ once: true }}
|
|
211
|
-
|
|
265
|
+
onobserve={(e) => (actionIntersecting = e.detail.isIntersecting)}
|
|
212
266
|
>
|
|
213
267
|
Hello world
|
|
214
268
|
</div>
|
|
215
269
|
```
|
|
216
270
|
|
|
217
|
-
Options passed to `use:intersect` are reactive
|
|
271
|
+
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
272
|
|
|
219
|
-
###
|
|
273
|
+
### `intersectAttachment` attachment
|
|
274
|
+
|
|
275
|
+
As of Svelte 5.29, [attachments](https://svelte.dev/docs/svelte/svelte-attachments) are the preferred replacement for actions. `intersectAttachment` wraps the `intersect` action with `svelte/attachments`'s `fromAction`, reusing the same observer logic but plugging into `{@attach ...}` instead of `use:`.
|
|
276
|
+
|
|
277
|
+
Attachments have a few architectural advantages over actions:
|
|
278
|
+
|
|
279
|
+
- No separate `update()` lifecycle method; they rerun reactively like a `$effect`
|
|
280
|
+
- Just plain functions, so they're easier to compose and generate dynamically
|
|
281
|
+
- Can be forwarded through components as ordinary props, unlike actions
|
|
282
|
+
|
|
283
|
+
```svelte
|
|
284
|
+
<script>
|
|
285
|
+
import { intersectAttachment } from "svelte-intersection-observer";
|
|
286
|
+
|
|
287
|
+
let attachmentIntersecting = $state(false);
|
|
288
|
+
</script>
|
|
289
|
+
|
|
290
|
+
<header class:intersecting={attachmentIntersecting}>
|
|
291
|
+
{attachmentIntersecting ? "Element is in view" : "Element is not in view"}
|
|
292
|
+
</header>
|
|
293
|
+
|
|
294
|
+
<div
|
|
295
|
+
{@attach intersectAttachment(() => ({ once: true }))}
|
|
296
|
+
onobserve={(e) => (attachmentIntersecting = e.detail.isIntersecting)}
|
|
297
|
+
>
|
|
298
|
+
Hello world
|
|
299
|
+
</div>
|
|
300
|
+
```
|
|
220
301
|
|
|
221
|
-
|
|
302
|
+
**Note**: unlike `use:intersect`, which takes the options object directly, `intersectAttachment` takes a function that _returns_ the options object (this is how `fromAction` tracks reactive dependencies). `intersect` remains fully supported; use whichever fits your codebase.
|
|
222
303
|
|
|
223
|
-
|
|
304
|
+
### Multiple elements
|
|
305
|
+
|
|
306
|
+
For performance, use `MultipleIntersectionObserver` to observe multiple elements with a single shared observer instead of instantiating a new one for every element.
|
|
224
307
|
|
|
225
308
|
```svelte
|
|
226
309
|
<script>
|
|
227
310
|
import { MultipleIntersectionObserver } from "svelte-intersection-observer";
|
|
228
311
|
|
|
229
|
-
let ref1;
|
|
230
|
-
let ref2;
|
|
312
|
+
let ref1 = $state();
|
|
313
|
+
let ref2 = $state();
|
|
231
314
|
|
|
232
|
-
|
|
315
|
+
let elements = $derived([ref1, ref2]);
|
|
233
316
|
</script>
|
|
234
317
|
|
|
235
|
-
<MultipleIntersectionObserver {elements}
|
|
236
|
-
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
318
|
+
<MultipleIntersectionObserver {elements}>
|
|
319
|
+
{#snippet children({ elementIntersections })}
|
|
320
|
+
<header>
|
|
321
|
+
<div class:intersecting={elementIntersections.get(ref1)}>
|
|
322
|
+
Item 1: {elementIntersections.get(ref1) ? "✓" : "✗"}
|
|
323
|
+
</div>
|
|
324
|
+
<div class:intersecting={elementIntersections.get(ref2)}>
|
|
325
|
+
Item 2: {elementIntersections.get(ref2) ? "✓" : "✗"}
|
|
326
|
+
</div>
|
|
327
|
+
</header>
|
|
244
328
|
|
|
245
|
-
|
|
246
|
-
|
|
329
|
+
<div bind:this={ref1}>Item 1</div>
|
|
330
|
+
<div bind:this={ref2}>Item 2</div>
|
|
331
|
+
{/snippet}
|
|
247
332
|
</MultipleIntersectionObserver>
|
|
248
333
|
```
|
|
249
334
|
|
|
250
335
|
### Using with `#each`
|
|
251
336
|
|
|
252
|
-
`MultipleIntersectionObserver` also handles a dynamic, `#each`-driven list
|
|
337
|
+
`MultipleIntersectionObserver` also handles a dynamic, `#each`-driven list: give every item its own slot in an array/object instead of one shared variable.
|
|
253
338
|
|
|
254
339
|
```svelte
|
|
255
340
|
<script>
|
|
256
341
|
import { MultipleIntersectionObserver } from "svelte-intersection-observer";
|
|
257
342
|
|
|
258
|
-
let items =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
];
|
|
343
|
+
let items = Array.from({ length: 10 }, (_, i) => ({
|
|
344
|
+
id: i + 1,
|
|
345
|
+
text: `Item ${i + 1}`,
|
|
346
|
+
}));
|
|
263
347
|
|
|
264
|
-
let refs = [];
|
|
265
|
-
let itemsContainer;
|
|
348
|
+
let refs = $state([]);
|
|
349
|
+
let itemsContainer = $state();
|
|
266
350
|
|
|
267
|
-
|
|
351
|
+
let itemElements = $derived(refs);
|
|
268
352
|
</script>
|
|
269
353
|
|
|
270
|
-
<MultipleIntersectionObserver
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
>
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
354
|
+
<MultipleIntersectionObserver elements={itemElements} root={itemsContainer}>
|
|
355
|
+
{#snippet children({ elementIntersections })}
|
|
356
|
+
<header>
|
|
357
|
+
{#each items as item, i (item.id)}
|
|
358
|
+
<div class:intersecting={elementIntersections.get(refs[i])}>
|
|
359
|
+
{item.text}: {elementIntersections.get(refs[i]) ? "✓" : "✗"}
|
|
360
|
+
</div>
|
|
361
|
+
{/each}
|
|
362
|
+
</header>
|
|
363
|
+
|
|
364
|
+
<div
|
|
365
|
+
bind:this={itemsContainer}
|
|
366
|
+
style="height: 150px; overflow-y: auto; display: flex; flex-direction: column; gap: 1rem;"
|
|
367
|
+
>
|
|
368
|
+
{#each items as item, i (item.id)}
|
|
369
|
+
<div
|
|
370
|
+
bind:this={refs[i]}
|
|
371
|
+
style="height: 100px; display: flex; align-items: center; flex-shrink: 0;"
|
|
372
|
+
>
|
|
373
|
+
{item.text}
|
|
374
|
+
</div>
|
|
375
|
+
{/each}
|
|
376
|
+
</div>
|
|
377
|
+
{/snippet}
|
|
293
378
|
</MultipleIntersectionObserver>
|
|
294
379
|
```
|
|
295
380
|
|
|
296
|
-
As with the scroll-to-end example, `root` must be an element that scrolls on its own
|
|
381
|
+
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
382
|
|
|
298
383
|
**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
384
|
|
|
@@ -315,16 +400,16 @@ As with the scroll-to-end example, `root` must be an element that scrolls on its
|
|
|
315
400
|
| observer | `IntersectionObserver` instance | `null` or [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) | `null` |
|
|
316
401
|
| skip | Pause observing without losing `entry`/`intersecting` state | `boolean` | `false` |
|
|
317
402
|
|
|
318
|
-
**Note**: the observed `element` must render with a non-zero width and height for `threshold` values greater than `0` to have any effect
|
|
403
|
+
**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.
|
|
319
404
|
|
|
320
|
-
####
|
|
405
|
+
#### Callback props
|
|
321
406
|
|
|
322
|
-
- **
|
|
323
|
-
- **
|
|
407
|
+
- **onobserve**: called when the element is first observed or whenever an intersection change occurs
|
|
408
|
+
- **onintersect**: called when the element is intersecting the viewport
|
|
324
409
|
|
|
325
|
-
|
|
410
|
+
Both callbacks are called with an [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).
|
|
326
411
|
|
|
327
|
-
####
|
|
412
|
+
#### `children` snippet props
|
|
328
413
|
|
|
329
414
|
| Name | Type |
|
|
330
415
|
| :----------- | :------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -348,12 +433,12 @@ The `e.detail` dispatched by the `observe` and `intersect` events is an [`Inters
|
|
|
348
433
|
| observer | `IntersectionObserver` instance | `null` or [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) | `null` |
|
|
349
434
|
| skip | Pause observing all elements without losing state | `boolean` | `false` |
|
|
350
435
|
|
|
351
|
-
####
|
|
436
|
+
#### Callback props
|
|
352
437
|
|
|
353
|
-
- **
|
|
354
|
-
- **
|
|
438
|
+
- **onobserve**: called when an element is first observed or whenever an intersection change occurs
|
|
439
|
+
- **onintersect**: called when an element is intersecting the viewport
|
|
355
440
|
|
|
356
|
-
|
|
441
|
+
Both callbacks are called with:
|
|
357
442
|
|
|
358
443
|
```ts
|
|
359
444
|
{
|
|
@@ -362,7 +447,7 @@ The `e.detail` for both events includes:
|
|
|
362
447
|
}
|
|
363
448
|
```
|
|
364
449
|
|
|
365
|
-
####
|
|
450
|
+
#### `children` snippet props
|
|
366
451
|
|
|
367
452
|
| Name | Type |
|
|
368
453
|
| :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -370,22 +455,24 @@ The `e.detail` for both events includes:
|
|
|
370
455
|
| elementIntersections | `Map<HTMLElement \| null, boolean>` |
|
|
371
456
|
| elementEntries | `Map<HTMLElement \| null,` [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)`>` |
|
|
372
457
|
|
|
373
|
-
### `intersect` action
|
|
458
|
+
### `intersect` action / `intersectAttachment` attachment
|
|
459
|
+
|
|
460
|
+
`intersectAttachment` is a thin wrapper around the `intersect` action (see [above](#intersectattachment-attachment)), so both share the same options and dispatched events.
|
|
374
461
|
|
|
375
462
|
#### Options
|
|
376
463
|
|
|
377
|
-
| Name | Description
|
|
378
|
-
| :--------- |
|
|
379
|
-
| root | Containing element
|
|
380
|
-
| rootMargin | Margin offset of the containing element
|
|
381
|
-
| threshold | Percentage of element visibility to trigger an event
|
|
382
|
-
| once | Unobserve the element after the first intersection event
|
|
383
|
-
| skip | Pause observing without disconnecting the observer
|
|
464
|
+
| Name | Description | Type | Default value |
|
|
465
|
+
| :--------- | :------------------------------------------------------- | :----------------------------------------------------------------- | :------------ |
|
|
466
|
+
| root | Containing element | `null` or `HTMLElement` | `null` |
|
|
467
|
+
| rootMargin | Margin offset of the containing element | `string` | `"0px"` |
|
|
468
|
+
| 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` |
|
|
469
|
+
| once | Unobserve the element after the first intersection event | `boolean` | `false` |
|
|
470
|
+
| skip | Pause observing without disconnecting the observer | `boolean` | `false` |
|
|
384
471
|
|
|
385
472
|
#### Dispatched events
|
|
386
473
|
|
|
387
|
-
- **
|
|
388
|
-
- **
|
|
474
|
+
- **onobserve**: fired on the element when it is first observed or whenever an intersection change occurs
|
|
475
|
+
- **onintersect**: fired on the element when it is intersecting the viewport
|
|
389
476
|
|
|
390
477
|
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
478
|
|
|
@@ -438,13 +525,5 @@ interface IntersectionObserverEntry {
|
|
|
438
525
|
|
|
439
526
|
</details>
|
|
440
527
|
|
|
441
|
-
## Changelog
|
|
442
|
-
|
|
443
|
-
[Changelog](CHANGELOG.md)
|
|
444
|
-
|
|
445
|
-
## License
|
|
446
|
-
|
|
447
|
-
[MIT](LICENSE)
|
|
448
|
-
|
|
449
528
|
[npm]: https://img.shields.io/npm/v/svelte-intersection-observer.svg?color=%23ff3e00&style=for-the-badge
|
|
450
529
|
[npm-url]: https://npmjs.com/package/svelte-intersection-observer
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { default } from "./IntersectionObserver.svelte";
|
|
2
|
-
export type { IntersectActionOptions } from "./intersect.js";
|
|
3
|
-
export { intersect } from "./intersect.js";
|
|
2
|
+
export type { IntersectActionOptions } from "./intersect.svelte.js";
|
|
3
|
+
export { intersect, intersectAttachment } from "./intersect.svelte.js";
|
|
4
4
|
export { default as MultipleIntersectionObserver } from "./MultipleIntersectionObserver.svelte";
|
package/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default } from "./IntersectionObserver.svelte";
|
|
2
|
-
export { intersect } from "./intersect.js";
|
|
2
|
+
export { intersect, intersectAttachment } from "./intersect.svelte.js";
|
|
3
3
|
export { default as MultipleIntersectionObserver } from "./MultipleIntersectionObserver.svelte";
|
|
@@ -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
|
/**
|
|
@@ -39,15 +40,32 @@ export interface IntersectActionOptions {
|
|
|
39
40
|
/**
|
|
40
41
|
* Svelte action that observes the element with the Intersection Observer API.
|
|
41
42
|
* Dispatches `observe` (on every change) and `intersect` (on entering the
|
|
42
|
-
* viewport) `CustomEvent`s on the element — listen with `
|
|
43
|
+
* viewport) `CustomEvent`s on the element — listen with `onobserve`/`onintersect`.
|
|
43
44
|
*/
|
|
44
45
|
export const intersect: Action<
|
|
45
46
|
HTMLElement,
|
|
46
47
|
IntersectActionOptions | undefined,
|
|
47
48
|
{
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
onobserve?: (event: CustomEvent<IntersectionObserverEntry>) => void;
|
|
50
|
+
onintersect?: (
|
|
50
51
|
event: CustomEvent<IntersectionObserverEntry & { isIntersecting: true }>,
|
|
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
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { fromAction } from "svelte/attachments";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Svelte action that observes `node` with the Intersection Observer API.
|
|
3
5
|
* Dispatches `observe` (on every change) and `intersect` (on entering the
|
|
4
|
-
* viewport) `CustomEvent`s on `node` — listen with `
|
|
6
|
+
* viewport) `CustomEvent`s on `node` — listen with `onobserve`/`onintersect`.
|
|
5
7
|
* @param {HTMLElement} node
|
|
6
|
-
* @param {import("./intersect.d.ts").IntersectActionOptions} [options]
|
|
8
|
+
* @param {import("./intersect.svelte.d.ts").IntersectActionOptions} [options]
|
|
7
9
|
*/
|
|
8
10
|
export function intersect(node, options = {}) {
|
|
9
11
|
let {
|
|
@@ -36,7 +38,7 @@ export function intersect(node, options = {}) {
|
|
|
36
38
|
if (!skip) observer.observe(node);
|
|
37
39
|
|
|
38
40
|
return {
|
|
39
|
-
/** @param {import("./intersect.d.ts").IntersectActionOptions} [newOptions] */
|
|
41
|
+
/** @param {import("./intersect.svelte.d.ts").IntersectActionOptions} [newOptions] */
|
|
40
42
|
update(newOptions = {}) {
|
|
41
43
|
once = newOptions.once ?? false;
|
|
42
44
|
|
|
@@ -67,3 +69,13 @@ export function intersect(node, options = {}) {
|
|
|
67
69
|
},
|
|
68
70
|
};
|
|
69
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Svelte attachment that observes the element with the Intersection Observer
|
|
75
|
+
* API. Equivalent to the `intersect` action, for use with `{@attach}` instead
|
|
76
|
+
* of `use:`. Wraps `intersect` via `svelte/attachments`'s `fromAction`.
|
|
77
|
+
* @param {() => import("./intersect.svelte.d.ts").IntersectActionOptions} [getOptions]
|
|
78
|
+
*/
|
|
79
|
+
export function intersectAttachment(getOptions = () => ({})) {
|
|
80
|
+
return fromAction(intersect, getOptions);
|
|
81
|
+
}
|
package/package.json
CHANGED