When you already have a ref to a node, onVisible(node, fn, opts?)
calls fn with each real IntersectionObserverEntry — the
side-effect half of the pota/use/intersection
emitter pair. For a reactive accessor use
useVisible; to attach an observer
declaratively use visible.
fn is never invoked with the pre-observer placeholder: the emitter
emits undefined before the first real entry arrives, and onVisible
filters that out. With opts.once, fn runs only for the first entry
where isIntersecting is true — exit entries before it, and
everything after it, are ignored.
| Argument | Type | Description |
|---|---|---|
node |
Element |
Element to observe. |
fn |
(entry: IntersectionObserverEntry) => void |
Called on each real intersection change. |
opts |
IntersectionObserverInit & { once?: boolean } |
Optional. Standard root / rootMargin / threshold, plus once to fire only on first arrival. |
Returns: void.
Logs whenever the node enters or leaves the viewport. The subscription
happens inside a use:ref function, where the node actually exists;
entry is always a real IntersectionObserverEntry.
import { render, signal } from 'pota'
import { onVisible } from 'pota/use/intersection'
function Panel() {
const log = signal('')
const watch = node =>
onVisible(node, entry =>
log.write(`visible: ${entry.isIntersecting}`),
)
return (
<div>
<div
use:ref={watch}
style={{ height: '120vh' }}
>
scroll me
</div>
<p>{log.read}</p>
</div>
)
}
render(Panel)
With once, fn runs a single time — the first entry where
isIntersecting is true. Exit entries before that, and everything
after it, are ignored.
import { render, signal } from 'pota'
import { onVisible } from 'pota/use/intersection'
function RevealOnce() {
const seen = signal(false)
return (
<div
use:ref={node =>
onVisible(node, () => seen.write(true), { once: true })
}
style={{ height: '40vh' }}
>
{() => (seen.read() ? 'arrived' : 'scroll me into view')}
</div>
)
}
render(RevealOnce)