Overview

onIntersect({ onVisible?, onHidden?, rootMargin?, threshold? }) wraps IntersectionObserver as an element directive. Callbacks fire with no entry argument — keep them lightweight.

onVisible(cb, opts?) and onHidden(cb, opts?) are shortcuts that only register one side of the transition.

onIntersect vs shortcuts

API
Fires on
Notes
onIntersect({…}) Enter and/or leave Full options object; both callbacks optional.
onVisible(cb, opts?) Enter only Handy for infinite-scroll sentinels and prefetch.
onHidden(cb, opts?) Leave only Pause media, stop work when off-screen.

Scroll-triggered reveal

onIntersect wraps IntersectionObserver as an element directive. Each item fades and slides in the first time it scrolls into view. The observer is automatically disconnected when the element is removed.

import { html, atom, onIntersect, renderApp } from 'mates';

const App = () => {
const visible = atom(new Set());
return () => html`
<div class="m-col m-gap">
${Array.from({ length: 12 }, (_, i) => html`
<div
${onIntersect({
onVisible: () => visible.set(s => new Set([...s, i])),
})}
class="card card-body"
style="
opacity:${visible().has(i) ? 1 : 0};
transform:translateY(${visible().has(i) ? 0 : 20}px);
transition:all .3s ${i * 50}ms
"
>
Item ${i + 1} — appeared when scrolled into view
</div>
`)}
</div>
`;
};
renderApp(App, document.getElementById('app'));

onVisible shortcut

onVisible(cb) fires when the element enters the viewport — handy for infinite-scroll sentinels.

import { html, atom, onVisible, renderApp } from 'mates';

const App = () => {
const hits = atom(0);
return () => html`
<div class="m-col m-gap">
<p class="label">Visible hits: ${hits()}</p>
${Array.from({ length: 8 }, (_, i) => html`
<div class="card card-body" ${onVisible(() => hits.set(n => n + 1))}>
Row ${i + 1}
</div>
`)}
</div>
`;
};
renderApp(App, document.getElementById('app'));