svelte-intersection-observer 2.1.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 +23 -23
- package/MultipleIntersectionObserver.svelte +41 -40
- package/README.md +424 -269
- 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 +72 -0
- package/intersect.svelte.js +70 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,24 +4,28 @@
|
|
|
4
4
|
[![NPM][npm]][npm-url]
|
|
5
5
|
<!-- HIDE_END -->
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## About
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`svelte-intersection-observer` is a zero-dependency Svelte library built on the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) that detects when an element enters or exits the viewport, without expensive scroll listeners. Use it for lazy-loading, scroll animations, infinite scroll, autoplaying video, impression tracking, and more (see [Use Cases](#use-cases)).
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
It offers six interchangeable primitives, all backed by the same shared observer logic.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
| Primitive | Export | Use it when... |
|
|
14
|
+
| :-------- | :----- | :--------------------- |
|
|
15
|
+
| [Component](#intersectionobserver) | `IntersectionObserver` | you want a component with a bound `intersecting` prop |
|
|
16
|
+
| [Pooled component](#multipleintersectionobserver) | `MultipleIntersectionObserver` | you're observing many elements and want one shared observer |
|
|
17
|
+
| [Action](#intersect) | `intersect` | you want `use:` on a plain element, no extra markup |
|
|
18
|
+
| [Attachment](#intersectattachment) | `intersectAttachment` | same as the action, composed via `{@attach}` (Svelte 5.29+) |
|
|
19
|
+
| [Composable](#createintersectionobserver) | `createIntersectionObserver` | you want reactive state from `<script>`, no directive |
|
|
20
|
+
| [Factory](#createintersectiongroup) | `createIntersectionGroup` | you're rendering a list with `{@attach}` and want one shared observer |
|
|
17
21
|
|
|
18
|
-
Try it in the [Svelte REPL](https://svelte.dev/repl/8cd2327a580c4f429c71f7df999bd51d).
|
|
22
|
+
See [Library](#library) for the full docs on each. Try it in the [Svelte REPL](https://svelte.dev/repl/8cd2327a580c4f429c71f7df999bd51d).
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
### Compatibility
|
|
21
25
|
|
|
22
26
|
| Package version | Svelte version | Notes |
|
|
23
27
|
| :--------------- | :----------------- | :----------------------------------------- |
|
|
24
|
-
| 1.x
|
|
28
|
+
| [1.x](https://github.com/metonym/svelte-intersection-observer/tree/v1.2.x) | 3, 4, 5 (non-runes) | Uses `export let`, slots, and `on:` events |
|
|
25
29
|
| 2.x | 5 (runes mode only) | Uses `$props()`, snippets, and callback props |
|
|
26
30
|
|
|
27
31
|
<!-- TOC -->
|
|
@@ -43,19 +47,21 @@ yarn add svelte-intersection-observer
|
|
|
43
47
|
|
|
44
48
|
```
|
|
45
49
|
|
|
46
|
-
##
|
|
50
|
+
## Library
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
Every primitive shares the same core options (`root`, `rootMargin`, `threshold`, `once`, `skip`) and the same `onobserve`/`onintersect` callbacks. Only how you plug it into your markup differs. See [Use Cases](#use-cases) for realistic scenarios built from these.
|
|
53
|
+
|
|
54
|
+
### `IntersectionObserver`
|
|
49
55
|
|
|
50
56
|
Use the [`bind:this`](https://svelte.dev/docs#bind_element) directive to pass an element reference to the `IntersectionObserver` component.
|
|
51
57
|
|
|
52
|
-
Then
|
|
58
|
+
Then bind to the reactive `intersecting` prop to check whether the element intersects the viewport.
|
|
53
59
|
|
|
54
60
|
```svelte
|
|
55
|
-
<script>
|
|
61
|
+
<script lang="ts">
|
|
56
62
|
import IntersectionObserver from "svelte-intersection-observer";
|
|
57
63
|
|
|
58
|
-
let element = $state();
|
|
64
|
+
let element: HTMLElement | undefined = $state();
|
|
59
65
|
let intersecting = $state(false);
|
|
60
66
|
</script>
|
|
61
67
|
|
|
@@ -68,42 +74,15 @@ Then, simply bind to the reactive `intersecting` prop to determine if the elemen
|
|
|
68
74
|
</IntersectionObserver>
|
|
69
75
|
```
|
|
70
76
|
|
|
71
|
-
|
|
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
|
-
|
|
98
|
-
### Once
|
|
77
|
+
#### `once`
|
|
99
78
|
|
|
100
|
-
Set `once` to `true`
|
|
79
|
+
Set `once` to `true` to unobserve the element after its first intersection event, useful for a one-time reveal animation, a single lazy-load, or a single analytics impression.
|
|
101
80
|
|
|
102
81
|
```svelte
|
|
103
|
-
<script>
|
|
82
|
+
<script lang="ts">
|
|
104
83
|
import IntersectionObserver from "svelte-intersection-observer";
|
|
105
84
|
|
|
106
|
-
let elementOnce = $state();
|
|
85
|
+
let elementOnce: HTMLElement | undefined = $state();
|
|
107
86
|
let intersectOnce = $state(false);
|
|
108
87
|
</script>
|
|
109
88
|
|
|
@@ -120,198 +99,71 @@ Set `once` to `true` for the intersection event to occur only once. The `element
|
|
|
120
99
|
</IntersectionObserver>
|
|
121
100
|
```
|
|
122
101
|
|
|
123
|
-
|
|
124
|
-
|
|
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.
|
|
126
|
-
|
|
127
|
-
```svelte no-eval
|
|
128
|
-
<script>
|
|
129
|
-
import IntersectionObserver from "svelte-intersection-observer";
|
|
130
|
-
|
|
131
|
-
let elementSkip = $state();
|
|
132
|
-
let paused = $state(false);
|
|
133
|
-
</script>
|
|
134
|
-
|
|
135
|
-
<button onclick={() => (paused = !paused)}>
|
|
136
|
-
{paused ? "Resume" : "Pause"}
|
|
137
|
-
</button>
|
|
138
|
-
|
|
139
|
-
<IntersectionObserver element={elementSkip} skip={paused}>
|
|
140
|
-
{#snippet children({ intersecting })}
|
|
141
|
-
<div bind:this={elementSkip}>{intersecting ? "In view" : "Not in view"}</div>
|
|
142
|
-
{/snippet}
|
|
143
|
-
</IntersectionObserver>
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### `onobserve` callback prop
|
|
147
|
-
|
|
148
|
-
`onobserve` is called when the element is first observed and also whenever an intersection event occurs.
|
|
149
|
-
|
|
150
|
-
```svelte no-eval
|
|
151
|
-
<IntersectionObserver
|
|
152
|
-
{element}
|
|
153
|
-
onobserve={(entry) => {
|
|
154
|
-
console.log(entry); // IntersectionObserverEntry
|
|
155
|
-
console.log(entry.isIntersecting); // true | false
|
|
156
|
-
}}
|
|
157
|
-
>
|
|
158
|
-
<div bind:this={element}>Hello world</div>
|
|
159
|
-
</IntersectionObserver>
|
|
160
|
-
```
|
|
102
|
+
#### `children` snippet
|
|
161
103
|
|
|
162
|
-
|
|
163
|
-
|
|
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.
|
|
165
|
-
|
|
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`.
|
|
167
|
-
|
|
168
|
-
```svelte no-eval
|
|
169
|
-
<IntersectionObserver
|
|
170
|
-
{element}
|
|
171
|
-
onintersect={(entry) => {
|
|
172
|
-
console.log(entry); // IntersectionObserverEntry
|
|
173
|
-
console.log(entry.isIntersecting); // true
|
|
174
|
-
}}
|
|
175
|
-
>
|
|
176
|
-
<div bind:this={element}>Hello world</div>
|
|
177
|
-
</IntersectionObserver>
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Detecting scroll to end
|
|
181
|
-
|
|
182
|
-
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.
|
|
183
|
-
|
|
184
|
-
**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.
|
|
185
|
-
|
|
186
|
-
```svelte
|
|
187
|
-
<script>
|
|
188
|
-
import IntersectionObserver from "svelte-intersection-observer";
|
|
189
|
-
|
|
190
|
-
let container = $state();
|
|
191
|
-
let sentinel = $state();
|
|
192
|
-
let reachedEnd = $state(false);
|
|
193
|
-
</script>
|
|
194
|
-
|
|
195
|
-
<header class:intersecting={reachedEnd}>
|
|
196
|
-
{reachedEnd ? "You've reached the end" : "Keep scrolling..."}
|
|
197
|
-
</header>
|
|
198
|
-
|
|
199
|
-
<div bind:this={container} style="height: 200px; overflow-y: auto;">
|
|
200
|
-
{#each Array.from({ length: 20 }) as _, i}
|
|
201
|
-
<p>Paragraph {i + 1}</p>
|
|
202
|
-
{/each}
|
|
203
|
-
|
|
204
|
-
<IntersectionObserver
|
|
205
|
-
element={sentinel}
|
|
206
|
-
root={container}
|
|
207
|
-
bind:intersecting={reachedEnd}
|
|
208
|
-
>
|
|
209
|
-
<div bind:this={sentinel} style="height: 1px;"></div>
|
|
210
|
-
</IntersectionObserver>
|
|
211
|
-
</div>
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Scrolling to a conditionally revealed element
|
|
104
|
+
An alternative to binding to the `intersecting` prop is to use the `children` snippet, which receives `intersecting`, `entry`, and `observer`.
|
|
215
105
|
|
|
216
|
-
|
|
106
|
+
In this example, "Hello world" fades in when its containing element intersects the viewport.
|
|
217
107
|
|
|
218
108
|
```svelte
|
|
219
|
-
<script>
|
|
109
|
+
<script lang="ts">
|
|
220
110
|
import IntersectionObserver from "svelte-intersection-observer";
|
|
221
|
-
import {
|
|
111
|
+
import { fade } from "svelte/transition";
|
|
222
112
|
|
|
223
|
-
let
|
|
224
|
-
let revealIntersecting = $state(false);
|
|
113
|
+
let node: HTMLElement | undefined = $state();
|
|
225
114
|
</script>
|
|
226
115
|
|
|
227
|
-
<header
|
|
228
|
-
<button
|
|
229
|
-
onclick={() => {
|
|
230
|
-
revealNode.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
231
|
-
}}
|
|
232
|
-
>
|
|
233
|
-
Scroll to section
|
|
234
|
-
</button>
|
|
235
|
-
</header>
|
|
116
|
+
<header></header>
|
|
236
117
|
|
|
237
|
-
<IntersectionObserver element={
|
|
238
|
-
|
|
239
|
-
{
|
|
240
|
-
|
|
241
|
-
Hello world
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
118
|
+
<IntersectionObserver element={node}>
|
|
119
|
+
{#snippet children({ intersecting })}
|
|
120
|
+
<div bind:this={node}>
|
|
121
|
+
{#if intersecting}
|
|
122
|
+
<div transition:fade={{ delay: 200 }}>Hello world</div>
|
|
123
|
+
{/if}
|
|
124
|
+
</div>
|
|
125
|
+
{/snippet}
|
|
245
126
|
</IntersectionObserver>
|
|
246
127
|
```
|
|
247
128
|
|
|
248
|
-
|
|
249
|
-
|
|
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.
|
|
251
|
-
|
|
252
|
-
```svelte
|
|
253
|
-
<script>
|
|
254
|
-
import { intersect } from "svelte-intersection-observer";
|
|
255
|
-
|
|
256
|
-
let actionIntersecting = $state(false);
|
|
257
|
-
</script>
|
|
258
|
-
|
|
259
|
-
<header class:intersecting={actionIntersecting}>
|
|
260
|
-
{actionIntersecting ? "Element is in view" : "Element is not in view"}
|
|
261
|
-
</header>
|
|
262
|
-
|
|
263
|
-
<div
|
|
264
|
-
use:intersect={{ once: true }}
|
|
265
|
-
onobserve={(e) => (actionIntersecting = e.detail.isIntersecting)}
|
|
266
|
-
>
|
|
267
|
-
Hello world
|
|
268
|
-
</div>
|
|
269
|
-
```
|
|
270
|
-
|
|
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.
|
|
272
|
-
|
|
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:
|
|
129
|
+
#### Props
|
|
278
130
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
131
|
+
| Name | Description | Type | Default value |
|
|
132
|
+
| :----------- | :---------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------ |
|
|
133
|
+
| element | Observed element | `null` or `HTMLElement` | `null` |
|
|
134
|
+
| once | Unobserve the element after the first intersection event | `boolean` | `false` |
|
|
135
|
+
| intersecting | `true` if the observed element is intersecting the viewport | `boolean` | `false` |
|
|
136
|
+
| root | Containing element | `null` or `HTMLElement` | `null` |
|
|
137
|
+
| rootMargin | Margin offset of the containing element | `string` | `"0px"` |
|
|
138
|
+
| 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` |
|
|
139
|
+
| entry | Observed element metadata | `null` or [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) | `null` |
|
|
140
|
+
| observer | `IntersectionObserver` instance | `null` or [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) | `null` |
|
|
141
|
+
| skip | Pause observing without losing `entry`/`intersecting` state | `boolean` | `false` |
|
|
282
142
|
|
|
283
|
-
|
|
284
|
-
<script>
|
|
285
|
-
import { intersectAttachment } from "svelte-intersection-observer";
|
|
143
|
+
**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.
|
|
286
144
|
|
|
287
|
-
|
|
288
|
-
</script>
|
|
145
|
+
#### Callback props
|
|
289
146
|
|
|
290
|
-
|
|
291
|
-
{attachmentIntersecting ? "Element is in view" : "Element is not in view"}
|
|
292
|
-
</header>
|
|
147
|
+
Same `onobserve`/`onintersect` behavior as described in [Callbacks](#callbacks-onobserve-and-onintersect) below.
|
|
293
148
|
|
|
294
|
-
|
|
295
|
-
{@attach intersectAttachment(() => ({ once: true }))}
|
|
296
|
-
onobserve={(e) => (attachmentIntersecting = e.detail.isIntersecting)}
|
|
297
|
-
>
|
|
298
|
-
Hello world
|
|
299
|
-
</div>
|
|
300
|
-
```
|
|
149
|
+
#### `children` snippet props
|
|
301
150
|
|
|
302
|
-
|
|
151
|
+
| Name | Type |
|
|
152
|
+
| :----------- | :------------------------------------------------------------------------------------------------------------------ |
|
|
153
|
+
| intersecting | `boolean` |
|
|
154
|
+
| entry | `null` or [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) |
|
|
155
|
+
| observer | [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) |
|
|
303
156
|
|
|
304
|
-
###
|
|
157
|
+
### `MultipleIntersectionObserver`
|
|
305
158
|
|
|
306
|
-
For performance, use `MultipleIntersectionObserver` to observe multiple elements with
|
|
159
|
+
For performance, use `MultipleIntersectionObserver` to observe multiple elements with one shared observer instead of instantiating one per element.
|
|
307
160
|
|
|
308
161
|
```svelte
|
|
309
|
-
<script>
|
|
162
|
+
<script lang="ts">
|
|
310
163
|
import { MultipleIntersectionObserver } from "svelte-intersection-observer";
|
|
311
164
|
|
|
312
|
-
let ref1 = $state();
|
|
313
|
-
let ref2 = $state();
|
|
314
|
-
|
|
165
|
+
let ref1: HTMLElement | undefined = $state();
|
|
166
|
+
let ref2: HTMLElement | undefined = $state();
|
|
315
167
|
let elements = $derived([ref1, ref2]);
|
|
316
168
|
</script>
|
|
317
169
|
|
|
@@ -332,22 +184,21 @@ For performance, use `MultipleIntersectionObserver` to observe multiple elements
|
|
|
332
184
|
</MultipleIntersectionObserver>
|
|
333
185
|
```
|
|
334
186
|
|
|
335
|
-
|
|
187
|
+
#### Using with `#each`
|
|
336
188
|
|
|
337
189
|
`MultipleIntersectionObserver` also handles a dynamic, `#each`-driven list: give every item its own slot in an array/object instead of one shared variable.
|
|
338
190
|
|
|
339
191
|
```svelte
|
|
340
|
-
<script>
|
|
192
|
+
<script lang="ts">
|
|
341
193
|
import { MultipleIntersectionObserver } from "svelte-intersection-observer";
|
|
342
194
|
|
|
343
|
-
let items = Array.from({ length:
|
|
195
|
+
let items = Array.from({ length: 5 }, (_, i) => ({
|
|
344
196
|
id: i + 1,
|
|
345
197
|
text: `Item ${i + 1}`,
|
|
346
198
|
}));
|
|
347
199
|
|
|
348
|
-
let refs = $state([]);
|
|
349
|
-
let itemsContainer = $state();
|
|
350
|
-
|
|
200
|
+
let refs: (HTMLElement | undefined)[] = $state([]);
|
|
201
|
+
let itemsContainer: HTMLElement | undefined = $state();
|
|
351
202
|
let itemElements = $derived(refs);
|
|
352
203
|
</script>
|
|
353
204
|
|
|
@@ -380,49 +231,12 @@ For performance, use `MultipleIntersectionObserver` to observe multiple elements
|
|
|
380
231
|
|
|
381
232
|
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`.
|
|
382
233
|
|
|
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
|
|
384
|
-
|
|
385
|
-
## API
|
|
386
|
-
|
|
387
|
-
### IntersectionObserver
|
|
388
|
-
|
|
389
|
-
#### Props
|
|
390
|
-
|
|
391
|
-
| Name | Description | Type | Default value |
|
|
392
|
-
| :----------- | :---------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------ |
|
|
393
|
-
| element | Observed element | `null` or `HTMLElement` | `null` |
|
|
394
|
-
| once | Unobserve the element after the first intersection event | `boolean` | `false` |
|
|
395
|
-
| intersecting | `true` if the observed element is intersecting the viewport | `boolean` | `false` |
|
|
396
|
-
| root | Containing element | `null` or `HTMLElement` | `null` |
|
|
397
|
-
| rootMargin | Margin offset of the containing element | `string` | `"0px"` |
|
|
398
|
-
| 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` |
|
|
399
|
-
| entry | Observed element metadata | `null` or [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) | `null` |
|
|
400
|
-
| observer | `IntersectionObserver` instance | `null` or [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) | `null` |
|
|
401
|
-
| skip | Pause observing without losing `entry`/`intersecting` state | `boolean` | `false` |
|
|
402
|
-
|
|
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.
|
|
404
|
-
|
|
405
|
-
#### Callback props
|
|
406
|
-
|
|
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
|
|
409
|
-
|
|
410
|
-
Both callbacks are called with an [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).
|
|
411
|
-
|
|
412
|
-
#### `children` snippet props
|
|
413
|
-
|
|
414
|
-
| Name | Type |
|
|
415
|
-
| :----------- | :------------------------------------------------------------------------------------------------------------------ |
|
|
416
|
-
| intersecting | `boolean` |
|
|
417
|
-
| entry | `null` or [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) |
|
|
418
|
-
| observer | [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) |
|
|
419
|
-
|
|
420
|
-
### MultipleIntersectionObserver
|
|
234
|
+
**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, bound via `bind:this={node}` inside it). Every iteration overwrites the same `node`, so each observer keeps re-observing a moving target, which can cause an infinite update loop. Use `MultipleIntersectionObserver` with a per-item ref instead.
|
|
421
235
|
|
|
422
236
|
#### Props
|
|
423
237
|
|
|
424
238
|
| Name | Description | Type | Default value |
|
|
425
|
-
| :------------------- | :---------------------------------------------------- |
|
|
239
|
+
| :------------------- | :---------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :------------ |
|
|
426
240
|
| elements | Array of HTML elements to observe | `Array<HTMLElement \| null>` | `[]` |
|
|
427
241
|
| once | Unobserve elements after the first intersection event | `boolean` | `false` |
|
|
428
242
|
| root | Containing element | `null` or `HTMLElement` | `null` |
|
|
@@ -435,10 +249,7 @@ Both callbacks are called with an [`IntersectionObserverEntry`](https://develope
|
|
|
435
249
|
|
|
436
250
|
#### Callback props
|
|
437
251
|
|
|
438
|
-
|
|
439
|
-
- **onintersect**: called when an element is intersecting the viewport
|
|
440
|
-
|
|
441
|
-
Both callbacks are called with:
|
|
252
|
+
Called with:
|
|
442
253
|
|
|
443
254
|
```ts
|
|
444
255
|
{
|
|
@@ -447,6 +258,8 @@ Both callbacks are called with:
|
|
|
447
258
|
}
|
|
448
259
|
```
|
|
449
260
|
|
|
261
|
+
See [Callbacks](#callbacks-onobserve-and-onintersect) for when each one fires.
|
|
262
|
+
|
|
450
263
|
#### `children` snippet props
|
|
451
264
|
|
|
452
265
|
| Name | Type |
|
|
@@ -455,9 +268,32 @@ Both callbacks are called with:
|
|
|
455
268
|
| elementIntersections | `Map<HTMLElement \| null, boolean>` |
|
|
456
269
|
| elementEntries | `Map<HTMLElement \| null,` [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)`>` |
|
|
457
270
|
|
|
458
|
-
### `intersect`
|
|
271
|
+
### `intersect`
|
|
272
|
+
|
|
273
|
+
As an alternative to the `IntersectionObserver` component, use the `intersect` action to observe an element directly with `use:`, without a `bind:this` reference or extra markup. Listen for `onobserve`/`onintersect` on the observed element itself.
|
|
274
|
+
|
|
275
|
+
```svelte
|
|
276
|
+
<script lang="ts">
|
|
277
|
+
import { intersect } from "svelte-intersection-observer";
|
|
278
|
+
|
|
279
|
+
let actionIntersecting = $state(false);
|
|
280
|
+
</script>
|
|
281
|
+
|
|
282
|
+
<header class:intersecting={actionIntersecting}>
|
|
283
|
+
{actionIntersecting ? "Element is in view" : "Element is not in view"}
|
|
284
|
+
</header>
|
|
285
|
+
|
|
286
|
+
<div
|
|
287
|
+
use:intersect={{ once: true }}
|
|
288
|
+
onobserve={(e) => {
|
|
289
|
+
actionIntersecting = e.detail.isIntersecting;
|
|
290
|
+
}}
|
|
291
|
+
>
|
|
292
|
+
Hello world
|
|
293
|
+
</div>
|
|
294
|
+
```
|
|
459
295
|
|
|
460
|
-
`
|
|
296
|
+
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.
|
|
461
297
|
|
|
462
298
|
#### Options
|
|
463
299
|
|
|
@@ -471,12 +307,331 @@ Both callbacks are called with:
|
|
|
471
307
|
|
|
472
308
|
#### Dispatched events
|
|
473
309
|
|
|
474
|
-
|
|
475
|
-
|
|
310
|
+
Same `onobserve`/`onintersect` behavior as described in [Callbacks](#callbacks-onobserve-and-onintersect); the action dispatches them on the element, and `e.detail` is the [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).
|
|
311
|
+
|
|
312
|
+
### `intersectAttachment`
|
|
313
|
+
|
|
314
|
+
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:`.
|
|
315
|
+
|
|
316
|
+
Attachments have a few architectural advantages over actions:
|
|
317
|
+
|
|
318
|
+
- No separate `update()` lifecycle method; they rerun reactively like a `$effect`
|
|
319
|
+
- Just plain functions, so they're easier to compose and generate dynamically
|
|
320
|
+
- Can be forwarded through components as ordinary props, unlike actions
|
|
321
|
+
|
|
322
|
+
```svelte
|
|
323
|
+
<script lang="ts">
|
|
324
|
+
import { intersectAttachment } from "svelte-intersection-observer";
|
|
325
|
+
|
|
326
|
+
let attachmentIntersecting = $state(false);
|
|
327
|
+
</script>
|
|
328
|
+
|
|
329
|
+
<header class:intersecting={attachmentIntersecting}>
|
|
330
|
+
{attachmentIntersecting ? "Element is in view" : "Element is not in view"}
|
|
331
|
+
</header>
|
|
332
|
+
|
|
333
|
+
<div
|
|
334
|
+
{@attach intersectAttachment(() => ({ once: true }))}
|
|
335
|
+
onobserve={(e) => {
|
|
336
|
+
attachmentIntersecting = e.detail.isIntersecting;
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
Hello world
|
|
340
|
+
</div>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**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.
|
|
344
|
+
|
|
345
|
+
Options and dispatched events are identical to the [`intersect` action](#intersect) above.
|
|
346
|
+
|
|
347
|
+
### `createIntersectionObserver`
|
|
348
|
+
|
|
349
|
+
To get intersection state without wrapping markup in a component, use `createIntersectionObserver`, a script-only rune-based composable: call it in `<script>` for reactive `intersecting`/`entry` getters, then apply `attach` to the node with `{@attach}`.
|
|
350
|
+
|
|
351
|
+
```svelte no-eval
|
|
352
|
+
<script lang="ts">
|
|
353
|
+
import { createIntersectionObserver } from "svelte-intersection-observer";
|
|
354
|
+
|
|
355
|
+
const observer = createIntersectionObserver(() => ({ threshold: 0.5 }));
|
|
356
|
+
</script>
|
|
357
|
+
|
|
358
|
+
<div {@attach observer.attach}>
|
|
359
|
+
{observer.intersecting ? "In view" : "Not in view"}
|
|
360
|
+
</div>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`createIntersectionObserver` takes the same options as [`intersectAttachment`](#intersectattachment) (as a function returning the options object) and reuses its underlying observer logic.
|
|
364
|
+
|
|
365
|
+
#### Return value
|
|
366
|
+
|
|
367
|
+
| Name | Description | Type |
|
|
368
|
+
| :----------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
|
|
369
|
+
| intersecting | `true` if the observed element is intersecting the viewport | `boolean` |
|
|
370
|
+
| entry | Observed element metadata | `null` or [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) |
|
|
371
|
+
| attach | Attachment to apply to the observed element via `{@attach}` | [`Attachment<HTMLElement>`](https://svelte.dev/docs/svelte/svelte-attachments) |
|
|
372
|
+
|
|
373
|
+
### `createIntersectionGroup`
|
|
374
|
+
|
|
375
|
+
A bare `intersect`/`intersectAttachment` inside an `#each` block creates one native `IntersectionObserver` per iteration: for a long list, that's N observers instead of 1. `createIntersectionGroup` fixes this for the action/attachment API: call it once to create a group, then call `group.attach(...)` once per element to get an attachment that shares a single underlying observer across the whole group.
|
|
376
|
+
|
|
377
|
+
```svelte
|
|
378
|
+
<script lang="ts">
|
|
379
|
+
import { createIntersectionGroup } from "svelte-intersection-observer";
|
|
380
|
+
|
|
381
|
+
let groupItems = $state(
|
|
382
|
+
Array.from({ length: 5 }, (_, i) => ({ id: i, intersecting: false })),
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const group = createIntersectionGroup();
|
|
386
|
+
</script>
|
|
387
|
+
|
|
388
|
+
<header>
|
|
389
|
+
{#each groupItems as item (item.id)}
|
|
390
|
+
<div class:intersecting={item.intersecting}>
|
|
391
|
+
Item {item.id}: {item.intersecting ? "✓" : "✗"}
|
|
392
|
+
</div>
|
|
393
|
+
{/each}
|
|
394
|
+
</header>
|
|
395
|
+
|
|
396
|
+
{#each groupItems as item (item.id)}
|
|
397
|
+
<div
|
|
398
|
+
class:intersecting={item.intersecting}
|
|
399
|
+
{@attach group.attach({
|
|
400
|
+
onobserve: (entry) => (item.intersecting = entry.isIntersecting),
|
|
401
|
+
})}
|
|
402
|
+
>
|
|
403
|
+
Item {item.id}
|
|
404
|
+
</div>
|
|
405
|
+
{/each}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
`root`/`rootMargin`/`threshold` configure the one shared observer, so they're passed once to `createIntersectionGroup` itself (as a function, for the same reactive-dependency-tracking reason `intersectAttachment` takes one) rather than per element:
|
|
409
|
+
|
|
410
|
+
```js
|
|
411
|
+
const group = createIntersectionGroup(() => ({
|
|
412
|
+
root: container,
|
|
413
|
+
rootMargin: "0px",
|
|
414
|
+
threshold: 0.5,
|
|
415
|
+
}));
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
`once`, `skip`, `onobserve`, and `onintersect` are the only options that make sense per element, so those are what `group.attach(...)` accepts.
|
|
419
|
+
|
|
420
|
+
#### Signature
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
function createIntersectionGroup(
|
|
424
|
+
getSharedOptions?: () => IntersectGroupSharedOptions,
|
|
425
|
+
): IntersectionGroup;
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
#### Shared options
|
|
429
|
+
|
|
430
|
+
Passed once to `createIntersectionGroup`; apply to every element in the group.
|
|
431
|
+
|
|
432
|
+
| Name | Description | Type | Default value |
|
|
433
|
+
| :--------- | :----------------------------------------------------- | :----------------------------------------------------------------- | :------------ |
|
|
434
|
+
| root | Containing element | `null` or `HTMLElement` | `null` |
|
|
435
|
+
| rootMargin | Margin offset of the containing element | `string` | `"0px"` |
|
|
436
|
+
| 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` |
|
|
437
|
+
|
|
438
|
+
#### `group.attach(options)` per-node options
|
|
439
|
+
|
|
440
|
+
Passed once per element, to `group.attach(...)`.
|
|
441
|
+
|
|
442
|
+
| Name | Description | Type | Default value |
|
|
443
|
+
| :--------- | :---------------------------------------------------------- | :------------------------------------------------------- | :------------ |
|
|
444
|
+
| once | Unobserve the element after the first intersection event | `boolean` | `false` |
|
|
445
|
+
| skip | Skip observing this element without affecting the group | `boolean` | `false` |
|
|
446
|
+
| onobserve | Called when the element is first observed or when an intersection change occurs | `(entry: IntersectionObserverEntry) => void` | `undefined` |
|
|
447
|
+
| onintersect | Called when the element is intersecting the viewport | `(entry: IntersectionObserverEntry) => void` | `undefined` |
|
|
448
|
+
|
|
449
|
+
#### Callbacks: `onobserve` and `onintersect`
|
|
450
|
+
|
|
451
|
+
Every primitive above exposes the same two callbacks, called with an [`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) (components pass it directly; action and attachment dispatch it as `event.detail`):
|
|
452
|
+
|
|
453
|
+
- **onobserve**: called when the element is first observed, and again on every intersection change
|
|
454
|
+
- **onintersect**: called only when the element is intersecting the viewport (a filtered view of `onobserve`)
|
|
455
|
+
|
|
456
|
+
```svelte no-eval
|
|
457
|
+
<IntersectionObserver
|
|
458
|
+
{element}
|
|
459
|
+
onobserve={(entry) => {
|
|
460
|
+
console.log(entry); // IntersectionObserverEntry
|
|
461
|
+
console.log(entry.isIntersecting); // true | false
|
|
462
|
+
}}
|
|
463
|
+
onintersect={(entry) => {
|
|
464
|
+
console.log(entry.isIntersecting); // always true
|
|
465
|
+
}}
|
|
466
|
+
>
|
|
467
|
+
<div bind:this={element}>Hello world</div>
|
|
468
|
+
</IntersectionObserver>
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## Use Cases
|
|
472
|
+
|
|
473
|
+
Realistic scenarios built from the primitives above.
|
|
474
|
+
|
|
475
|
+
### Lazy-loading images
|
|
476
|
+
|
|
477
|
+
Delay loading an image's real `src` until it's about to scroll into view. `rootMargin` starts the fetch slightly before the image is visible so it's ready when the user scrolls to it; `once` stops observing once it has loaded.
|
|
478
|
+
|
|
479
|
+
```svelte no-eval
|
|
480
|
+
<script lang="ts">
|
|
481
|
+
import { intersectAttachment } from "svelte-intersection-observer";
|
|
482
|
+
|
|
483
|
+
let loaded = $state(false);
|
|
484
|
+
</script>
|
|
485
|
+
|
|
486
|
+
<img
|
|
487
|
+
{@attach intersectAttachment(() => ({ once: true, rootMargin: "200px" }))}
|
|
488
|
+
onintersect={() => (loaded = true)}
|
|
489
|
+
src={loaded ? "/photo.jpg" : "/placeholder.jpg"}
|
|
490
|
+
alt=""
|
|
491
|
+
/>
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Autoplaying video
|
|
495
|
+
|
|
496
|
+
Play a `<video>` while it's on screen and pause it once it scrolls away. Unlike lazy-loading, this needs to react every time visibility changes, so use `onobserve` (not `onintersect`) and skip `once`.
|
|
497
|
+
|
|
498
|
+
```svelte no-eval
|
|
499
|
+
<script lang="ts">
|
|
500
|
+
import { intersect } from "svelte-intersection-observer";
|
|
501
|
+
|
|
502
|
+
let video: HTMLVideoElement | undefined = $state();
|
|
503
|
+
</script>
|
|
504
|
+
|
|
505
|
+
<video
|
|
506
|
+
bind:this={video}
|
|
507
|
+
use:intersect
|
|
508
|
+
onobserve={(e) => {
|
|
509
|
+
e.detail.isIntersecting ? video?.play() : video?.pause();
|
|
510
|
+
}}
|
|
511
|
+
src="/clip.mp4"
|
|
512
|
+
muted
|
|
513
|
+
loop
|
|
514
|
+
></video>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Tracking impressions
|
|
518
|
+
|
|
519
|
+
Fire an impression event the first time an element is meaningfully visible. `threshold` sets what counts as "visible enough," and `once` guarantees a single event per element.
|
|
520
|
+
|
|
521
|
+
```svelte no-eval
|
|
522
|
+
<div
|
|
523
|
+
use:intersect={{ once: true, threshold: 0.5 }}
|
|
524
|
+
onintersect={() => analytics.track("ad_impression", { id: "banner-1" })}
|
|
525
|
+
>
|
|
526
|
+
<!-- ad content -->
|
|
527
|
+
</div>
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Infinite scroll
|
|
531
|
+
|
|
532
|
+
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.
|
|
533
|
+
|
|
534
|
+
**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.
|
|
535
|
+
|
|
536
|
+
```svelte
|
|
537
|
+
<script lang="ts">
|
|
538
|
+
import IntersectionObserver from "svelte-intersection-observer";
|
|
539
|
+
|
|
540
|
+
let container: HTMLElement | undefined = $state();
|
|
541
|
+
let sentinel: HTMLElement | undefined = $state();
|
|
542
|
+
let reachedEnd = $state(false);
|
|
543
|
+
</script>
|
|
544
|
+
|
|
545
|
+
<header class:intersecting={reachedEnd}>
|
|
546
|
+
{reachedEnd ? "You've reached the end" : "Keep scrolling..."}
|
|
547
|
+
</header>
|
|
548
|
+
|
|
549
|
+
<div bind:this={container} style="height: 200px; overflow-y: auto;">
|
|
550
|
+
{#each Array.from({ length: 20 }) as _, i}
|
|
551
|
+
<p>Paragraph {i + 1}</p>
|
|
552
|
+
{/each}
|
|
553
|
+
|
|
554
|
+
<IntersectionObserver
|
|
555
|
+
element={sentinel}
|
|
556
|
+
root={container}
|
|
557
|
+
bind:intersecting={reachedEnd}
|
|
558
|
+
>
|
|
559
|
+
<div bind:this={sentinel} style="height: 1px;"></div>
|
|
560
|
+
</IntersectionObserver>
|
|
561
|
+
</div>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
The same sentinel pattern powers infinite scroll: call a `loadMore()` function from `onintersect` instead of (or alongside) updating `reachedEnd`.
|
|
565
|
+
|
|
566
|
+
### Reveal animation on scroll
|
|
567
|
+
|
|
568
|
+
Keep the `bind:this` element outside the `{#if intersecting}` block, and only gate the animated content inside it. The bound element stays in the DOM even before the reveal fires, so external triggers like `scrollIntoView()` still work, and the animation replays every time the element crosses into or out of view.
|
|
569
|
+
|
|
570
|
+
```svelte
|
|
571
|
+
<script lang="ts">
|
|
572
|
+
import IntersectionObserver from "svelte-intersection-observer";
|
|
573
|
+
import { fly } from "svelte/transition";
|
|
574
|
+
|
|
575
|
+
let revealNode: HTMLElement | undefined = $state();
|
|
576
|
+
let revealIntersecting = $state(false);
|
|
577
|
+
</script>
|
|
578
|
+
|
|
579
|
+
<header class:intersecting={revealIntersecting}>
|
|
580
|
+
<p>{revealIntersecting ? "In view" : "Not in view"}</p>
|
|
581
|
+
<button
|
|
582
|
+
style="margin-top: 0.75rem;"
|
|
583
|
+
onclick={() => {
|
|
584
|
+
revealNode?.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
585
|
+
}}
|
|
586
|
+
>
|
|
587
|
+
Scroll to section
|
|
588
|
+
</button>
|
|
589
|
+
</header>
|
|
590
|
+
|
|
591
|
+
<IntersectionObserver element={revealNode} bind:intersecting={revealIntersecting}>
|
|
592
|
+
<div bind:this={revealNode}>
|
|
593
|
+
{#if revealIntersecting}
|
|
594
|
+
<section transition:fly={{ y: 50, duration: 300 }}>
|
|
595
|
+
Hello world
|
|
596
|
+
</section>
|
|
597
|
+
{/if}
|
|
598
|
+
</div>
|
|
599
|
+
</IntersectionObserver>
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### `skip`
|
|
603
|
+
|
|
604
|
+
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 toggles back and forth freely. `MultipleIntersectionObserver` and the `intersect` action support the same `skip` option.
|
|
605
|
+
|
|
606
|
+
```svelte no-eval
|
|
607
|
+
<script lang="ts">
|
|
608
|
+
import IntersectionObserver from "svelte-intersection-observer";
|
|
609
|
+
|
|
610
|
+
let elementSkip: HTMLElement | undefined = $state();
|
|
611
|
+
let paused = $state(false);
|
|
612
|
+
</script>
|
|
613
|
+
|
|
614
|
+
<button onclick={() => (paused = !paused)}>
|
|
615
|
+
{paused ? "Resume" : "Pause"}
|
|
616
|
+
</button>
|
|
617
|
+
|
|
618
|
+
<IntersectionObserver element={elementSkip} skip={paused}>
|
|
619
|
+
{#snippet children({ intersecting })}
|
|
620
|
+
<div bind:this={elementSkip}>{intersecting ? "In view" : "Not in view"}</div>
|
|
621
|
+
{/snippet}
|
|
622
|
+
</IntersectionObserver>
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### List strategy
|
|
626
|
+
|
|
627
|
+
Both [`MultipleIntersectionObserver`](#multipleintersectionobserver) and [`createIntersectionGroup`](#createintersectiongroup) share one observer across many elements. Pick based on how you want to wire it up:
|
|
628
|
+
|
|
629
|
+
- Use `MultipleIntersectionObserver` when you're fine wrapping the list in a component and want a ready-made `elementIntersections` map handed to you via the `children` snippet.
|
|
630
|
+
- Use `createIntersectionGroup` when you'd rather attach directly to each element with `{@attach}`, no extra component, and are happy tracking intersection state on your own per-item objects (as shown in its example above).
|
|
476
631
|
|
|
477
|
-
|
|
632
|
+
Either way, avoid giving `IntersectionObserver` a single shared `bind:this` variable inside `#each`; see the warning under [pooled component](#multipleintersectionobserver).
|
|
478
633
|
|
|
479
|
-
|
|
634
|
+
## `IntersectionObserverEntry`
|
|
480
635
|
|
|
481
636
|
Note that all properties in [IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) are read-only.
|
|
482
637
|
|