IntersectionObserver
Low-level utility for observing element intersection with viewport using the IntersectionObserver API. Provides fine-grained control over observation lifecycle.
Usage
Single element observation
Observe a single element by passing it as the first parameter:
import { Component, inject, ElementRef } from '@angular/core';
import { intersectionObserver } from '@signality/core';
@Component({
template: `<div>Scroll to see intersection!</div>`,
})
export class Intersection {
readonly el = inject(ElementRef);
readonly observer = intersectionObserver(this.el, console.log);
}Multiple elements observation
Observe multiple elements by passing an array:
import { Component, viewChild, ElementRef } from '@angular/core';
import { intersectionObserver } from '@signality/core';
@Component({
template: `
<div #child1>Child 1</div>
<div #child2>Child 2</div>
`,
})
export class MultipleIntersection {
readonly child1 = viewChild<ElementRef>('child1');
readonly child2 = viewChild<ElementRef>('child2');
readonly observer = intersectionObserver(
[this.child1, this.child2],
(entries, observer) => {
for (const entry of entries) {
console.log('Intersecting:', entry.isIntersecting);
}
}
);
}With reactive options
All options (root, rootMargin, threshold) can be reactive:
import { Component, signal, viewChild, ElementRef } from '@angular/core';
import { intersectionObserver } from '@signality/core';
@Component({
template: `
<div #container>
<div #box>Observed element</div>
</div>
<button (click)="threshold.set(threshold() === 0.5 ? 0.9 : 0.5)">
Toggle threshold
</button>
`,
})
export class ReactiveOptions {
readonly box = viewChild<ElementRef>('box');
readonly root = viewChild<ElementRef>('container');
readonly rootMargin = input('10px');
readonly threshold = signal(0.5);
readonly observer = intersectionObserver(
this.box,
entries => { /* callback */ },
{
root: this.root, // Reactive root element
rootMargin: this.rootMargin, // Reactive margin
threshold: this.threshold, // Reactive threshold
}
);
}Manual cleanup
Observers are automatically disconnected after the view is destroyed (see Automatic cleanup). However, the returned IntersectionObserverRef can be destroyed to stop observation manually:
import { Component, viewChild, ElementRef } from '@angular/core';
import { intersectionObserver } from '@signality/core';
@Component({
template: `<div #box>Scroll me!</div>`,
})
export class ManualCleanup {
readonly box = viewChild<ElementRef>('box');
readonly observer = intersectionObserver(this.box, console.log);
manualCleanup() {
// Stop observing
this.observer.destroy();
}
}Parameters
| Parameter | Type | Description |
|---|---|---|
target | MaybeElementSignal<Element> | MaybeElementSignal<Element>[] | Element(s) to observe. Can be a single element or array. Can be: - A plain Element or ElementRef<Element>- A Signal<Element> or Signal<ElementRef<Element>>- undefined (observation is skipped)- An array of any of the above |
callback | (entries: readonly IntersectionObserverEntry[], observer: IntersectionObserver) => void | Callback function called when observed elements intersect with viewport |
options | IntersectionObserverInitOptions | Optional configuration (see Options below) |
Options
The IntersectionObserverInitOptions extends Omit<CreateEffectOptions, 'allowSignalWrites'>:
| Option | Type | Default | Description |
|---|---|---|---|
root | MaybeElementSignal<Element> | Document | null | - | The element that is used as the viewport for checking visibility |
rootMargin | MaybeSignal<string> | - | Margin around the root element |
threshold | MaybeSignal<number | number[]> | - | Threshold(s) for intersection |
manualCleanup | boolean | false | If true, the effect requires manual cleanup. By default, the effect automatically registers itself for cleanup with the current DestroyRef |
debugName | string | - | Debug name for the effect (used in Angular DevTools) |
injector | Injector | - | Optional injector for DI context |
Return Value
Returns an IntersectionObserverRef with a destroy() method to stop observing the element(s).
Examples
Scroll animations
import { Component, viewChild, ElementRef } from '@angular/core';
import { intersectionObserver } from '@signality/core';
@Component({
template: `
<div #section [class.visible]="isVisible()">
Content that animates on scroll
</div>
`,
})
export class ScrollAnimation {
readonly section = viewChild<ElementRef>('section');
readonly isVisible = signal(false);
constructor() {
intersectionObserver(
this.section,
entries => {
for (const entry of entries) {
this.isVisible.set(entry.isIntersecting);
}
},
{ threshold: 0.1 }
);
}
}Conditional observation
Observe elements conditionally:
import { Component, viewChild, ElementRef, computed } from '@angular/core';
import { intersectionObserver } from '@signality/core';
@Component({
template: `
<div #box>Box</div>
<button (click)="enabled.set(!enabled())">
{{ enabled() ? 'Disable' : 'Enable' }} Observation
</button>
`,
})
export class ConditionalIntersection {
readonly box = viewChild<ElementRef>('box');
readonly enabled = signal(true);
readonly observer = intersectionObserver(
computed(() => (this.enabled() ? this.box() : undefined)),
(entries, observer) => {
console.log('Intersected:', entries);
}
);
}SSR Compatibility
On the server, intersectionObserver returns a no-op IntersectionObserverRef that safely handles destroy() calls without creating actual observers.
Type Definitions
interface IntersectionObserverInitOptions extends Omit<CreateEffectOptions, 'allowSignalWrites'> {
readonly root?: MaybeElementSignal<Element> | Document | null;
readonly rootMargin?: MaybeSignal<string>;
readonly threshold?: MaybeSignal<number | number[]>;
}
interface IntersectionObserverRef {
readonly destroy: () => void;
}
function intersectionObserver(
target: MaybeElementSignal<Element> | MaybeElementSignal<Element>[],
callback: (entries: readonly IntersectionObserverEntry[], observer: IntersectionObserver) => void,
options?: IntersectionObserverInitOptions
): IntersectionObserverRef;Related
- ElementVisibility — High-level reactive visibility tracking using IntersectionObserver
- ResizeObserver — Observe element size changes