Skip to content
60fps/ui

InView

InViewEngine observes the groups inside a root element and toggles a single attribute as each one enters or leaves the viewport. All animation is pure CSS — the engine never touches style, it only flips data-inview-visible / data-inview-hidden, and the stylesheet does the rest through custom properties. That keeps the JavaScript tiny and the same across every framework.

Everything below starts hidden — scroll inside the panel and each group reveals as it enters. The first card also reports its own visibility.

↓ scroll down to reveal
onVisibilityChangeOut of view · 0×

This card reports its own visibility

The engine emits a visibility event as the root enters and leaves the panel. Scroll it away and back to watch the counter climb.

fade-left

Directional entrance

Horizontal reveals mirror automatically when the document direction is RTL.

slide-up

99.9%

moves without fading — ideal for metrics

scale-down
settles into place
data-inview-repeat

Replays on every entry

Scroll past this card and back: it re-animates, while the groups above settle after their first reveal.

↑ scroll back up to replay

Everything below starts hidden — scroll inside the panel and each group reveals as it enters. The first card also reports its own visibility.

↓ scroll down to reveal
onVisibilityChange Out of view · 0×

This card reports its own visibility

The engine emits a visibility event as the root enters and leaves the panel. Scroll it away and back to watch the counter climb.

fade-left

Directional entrance

Horizontal reveals mirror automatically when the document direction is RTL.

slide-up

99.9%

moves without fading — ideal for metrics

scale-down
settles into place
data-inview-repeat

Replays on every entry

Scroll past this card and back: it re-animates, while the groups above settle after their first reveal.

↑ scroll back up to replay

Usage

Give the root class="inview-root" and call useInView(ref) — the engine marks the root with data-inview-scope itself. Inside, every data-inview group shares one visibility state, and each node opts into an animation with data-inview-anim:

import { useRef } from 'react';
import { useInView } from '@60fps/react/inview-engine';
import { inViewAnimStyle } from '@60fps/inview';

function Section() {
	const ref = useRef<HTMLDivElement>(null);
	useInView(ref);

	return (
		<div ref={ref} className="inview-root">
			<section data-inview>
				<h2 data-inview-anim="fade-up">Reveal me</h2>
				<p data-inview-anim="fade" style={inViewAnimStyle({ delay: 120 })}>
					…then me, a touch later.
				</p>
			</section>
		</div>
	);
}
<script setup lang="ts">
import { ref } from 'vue';
import { useInView } from '@60fps/vue/inview-engine';
import { inViewAnimStyle } from '@60fps/inview';

const root = ref<HTMLElement | null>(null);
useInView(root);
</script>

<template>
	<div ref="root" class="inview-root">
		<section data-inview>
			<h2 data-inview-anim="fade-up">Reveal me</h2>
			<p data-inview-anim="fade" :style="inViewAnimStyle({ delay: 120 })">…then me, a touch later.</p>
		</section>
	</div>
</template>

The markup contract

Attribute Type Default Description
data-inview on a group An observed group. Toggling its visibility cascades to descendants via CSS.
data-inview-anim fade | fade-up | fade-left | slide-up | scale-down Opt a node into one animation. Inherits the group visibility state.
data-inview-repeat on a group Replay every time the group re-enters, instead of settling after the first reveal.
data-inview-defer on a group Skip the enter animation for content already on screen at mount; animate only on later entries.
data-inview-threshold number 0 Per-group IntersectionObserver threshold override.
data-inview-margin string '0px 0px -10% 0px' Per-group rootMargin override.

useInView(ref, options?)

Option Type Default Description
threshold number | number[] 0 Threshold for the root-level `onVisibilityChange`.
rootMargin string Root margin for the root-level `onVisibilityChange`.
root Element | Document | null IntersectionObserver root for the observed groups. Defaults to the viewport.
onVisibilityChange (visible, entry) => void Called as the root element enters and leaves the viewport. Opts the root into observation.

Tuning with inViewAnimStyle

For per-node delays, offsets or one-off overrides, inViewAnimStyle returns a plain style object (React style or Vue :style) that sets the underlying custom properties:

inViewAnimStyle({ delay: 120, duration: 600, fromTranslateY: '28px' });
// → { '--inview-delay': '120ms', '--inview-duration': '600ms', '--inview-from-translate-y': '28px' }