In View
Reacts when its content scrolls into view: reveal it, load more, or pause it when it leaves.
c2-in-view watches its content scroll in and out of view, like Web Awesome’s intersection observer, Framer Motion’s whileInView and the “load more” sentinel at the end of a feed. It fires enter and leave, exposes the in-view state to CSS, and can reveal its content with a fade and a slide, all set with CSS variables.
Import
import '@c2n/components/in-view'Usage
Scroll the list: each card fades and rises as it comes into view.
Card 1
Card 1
Card 2
Card 2
Card 3
Card 3
Card 4
Card 4
Card 5
Card 5
Card 6
Card 6
Card 7
Card 7
Card 8
Card 8
Card 1
Card 1
Card 2
Card 2
Card 3
Card 3
Card 4
Card 4
Card 5
Card 5
Card 6
Card 6
Card 7
Card 7
Card 8
Card 8
Events and state
enter fires when the content comes into view and leave when it goes, each with { ratio }, the share of it that shows; inView reads the state now. threshold (0 to 1) is how much must show to count, and root-margin grows or shrinks the viewport, as CSS margins. once stops watching after the first entry, and disabled stops watching; content never seen while not watched shows as in view, so a reveal never leaves it hidden.
The state is a CSS custom state, not an attribute: style it with c2-in-view:state(in-view). Content inside a scrolling container, such as a c2-scroll-area, counts as in view only where the container shows it; root-margin applies to the page’s viewport, not to the container’s edge. The events do not bubble, so an in-view nested in another does not fire the outer one’s listeners.
Reveal
The reveal is CSS: --c2-in-view__hidden--opacity and --c2-in-view__hidden--transform hold the content’s look while it is out of view (by default, none), and it eases to normal as it enters, over --c2-in-view--transition-duration. --c2-in-view--transition-delay staggers a row. Under prefers-reduced-motion the content simply shows.
Loading more
Put one at the end of a list, with a margin so it fires before the reader gets there. enter fires when it comes into view, not again while it stays there, so keep loading while a page of results leaves it in view, one page at a time, and stop at the last page:
<c2-in-view root-margin="300px" id="more"></c2-in-view>
<script>
const more = document.getElementById('more')
let loading = false
more.addEventListener('enter', async () => {
if (loading) return
loading = true
// loadNextPage() resolves to whether there was a page to add.
while (more.inView && (await loadNextPage()));
loading = false
// No more pages: stop watching.
if (!more.inView) return
more.disabled = true
})
</script>