InView
Visibility detection via IntersectionObserver:
- Monitors elements entering and optionally leaving the viewport
- Staggered initial delays via
maxInitialDelayand element position - Direction detection for scroll-aware animations
- Optional CSS classes via
data-in-view-class
Observation lifecycle
InView runs two observation phases:
- Initial pass —
rootMarginis0%on all sides.isInitialStartistrue. Enter events use position-based stagger frommaxInitialDelay. - Ongoing — after the first enter batch, observers reconnect with
rootMargin(default0% 0% -5% 0%).isInitialStartbecomesfalse.
Two observers are used internally:
| Observer | Role | rootMargin |
|---|---|---|
| Enter | in callbacks, initial stagger | 0% on first pass, then rootMargin |
| Leave | out callbacks (when hasOut is true) | always 0px |
When hasOut is false, the element is unobserved after the first in — one-time triggers only.
Basic Demo
HTML
<div class="fullpage-center">
<div class="container">
<h1 class="animate" data-in-view-class="fadeInUp">InView</h1>
<p class="animate" data-in-view-class="fadeInUp">
Lorem
</p>
<p class="animate" data-in-view-class="fadeInUp">
ipsum
</p>
<p class="animate" data-in-view-class="fadeInUp">
dolor
</p>
<p class="animate" data-in-view-class="fadeInUp">
sit
</p>
<p class="animate" data-in-view-class="fadeInUp">
amet
</p>
<p class="animate" data-in-view-class="fadeInUp">
consectetur
</p>
<p class="animate" data-in-view-class="fadeInUp">
adipiscing
</p>
<p class="animate" data-in-view-class="fadeInUp">
elit
</p>
<p class="animate" data-in-view-class="fadeInUp">
sed
</p>
<p class="animate" data-in-view-class="fadeInUp">do</p>
<p class="animate" data-in-view-class="fadeInUp">
eiusmod
</p>
<p class="animate" data-in-view-class="fadeInUp">
Lorem
</p>
<p class="animate" data-in-view-class="fadeInUp">
ipsum
</p>
<p class="animate" data-in-view-class="fadeInUp">
dolor
</p>
<p class="animate" data-in-view-class="fadeInUp">
sit
</p>
<p class="animate" data-in-view-class="fadeInUp">
amet
</p>
<p class="animate" data-in-view-class="fadeInUp">
consectetur
</p>
<p class="animate" data-in-view-class="fadeInUp">
adipiscing
</p>
<p class="animate" data-in-view-class="fadeInUp">
elit
</p>
<p class="animate" data-in-view-class="fadeInUp">
sed
</p>
<p class="animate" data-in-view-class="fadeInUp">do</p>
<p class="animate" data-in-view-class="fadeInUp">
eiusmod
</p>
</div>
</div>
CSS
.container {
width: 100%;
padding: 0 1rem;
}
h1 {
margin: 0 0 1.25rem;
font-size: 2rem;
font-weight: 600;
}
p {
margin: 0.5em 0;
}
.animate {
opacity: 0;
}
.fadeInUp {
animation: fade-in-up;
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.61, 1, 0.88, 1);
animation-fill-mode: both;
}
@keyframes fade-in-up {
from {
transform: translateY(3rem);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
JavaScript
import {
InView
} from "vevet";
const instance = new InView({
hasOut: false,
maxInitialDelay: 700
});
const elements = document.querySelectorAll("*[data-in-view-class]");
elements.forEach(element => {
instance.addElement(element);
});
Initialization
InView is easy to initialize:
<h1 id="observable-element">Hello, World!</h1>
import { InView } from 'vevet';
const observer = new InView();
observer.on('in', ({ element, direction }) => {
console.log('Element entered from:', direction);
// direction can be: 'fromBottom', 'fromTop', 'fromLeft', or 'fromRight'
});
// addElement returns a cleanup function
const stopObserving = observer.addElement(
document.getElementById('observable-element'),
);
// Later, you can stop observing this specific element
stopObserving();
CSS Class Management
InView can automatically add and remove CSS classes based on element visibility. This enables fully CSS-driven scroll animations without JavaScript transitions.
To enable this behavior, use the data-in-view-class attribute:
Simple class (same animation for all directions):
<h1 data-in-view-class="fadeIn">Animated Element</h1>
Directional classes (different animations based on scroll direction):
<!-- Vertical scrolling: fadeInUp when from bottom, fadeInDown when from top -->
<h1 data-in-view-class="fadeInUp|fadeInDown">Directional InView</h1>
<!-- Horizontal scrolling: fadeInLeft when from right, fadeInRight when from left -->
<h1 data-in-view-class="fadeInLeft|fadeInRight">Horizontal Directional</h1>
The syntax is direct|reverse, where:
- direct is applied when the element enters from the default direction (bottom for vertical, right for horizontal)
- reverse is applied when entering from the opposite direction (top for vertical, left for horizontal)
If only one class is provided (no pipe), it's used for all directions.
The class from data-in-view-class is added when the element enters the viewport and removed once the element leaves the viewport (if hasOut: true).
This allows creating fully CSS-driven scroll animations without JavaScript transitions.
Managing Elements
Stop observing a specific element:
observer.removeElement(yourElement);
Destroy the InView instance:
observer.destroy();
Direction Detection
InView automatically detects the direction from which elements enter or leave the viewport. This information is available in callbacks and can be used to create directional animations.
The direction values are:
fromBottom- Element enters/leaves from below (vertical scrolling)fromTop- Element enters/leaves from above (vertical scrolling)fromLeft- Element enters/leaves from the left (horizontal scrolling)fromRight- Element enters/leaves from the right (horizontal scrolling)
Direction is determined by comparing the element's position to the viewport center. For the initial pass, the direction defaults to the natural scroll direction (fromBottom for vertical, fromRight for horizontal).
For horizontal layouts in RTL documents, initial stagger delay is mirrored so elements farther along the reading direction receive longer delays.
isInitialStart timingisInitialStart becomes false as soon as the first enter batch is processed — before staggered in callbacks fire. Do not rely on observer.isInitialStart inside the in handler to distinguish initial vs scroll animations; track that state yourself or set maxInitialDelay: 0 for scroll-only triggers.
Best Practices
- Prefer adding a single InView instance and registering multiple elements instead of creating many observers.
- Avoid observing extremely large numbers of elements — group content into sections when possible.
- Use
hasOut: falseif you only need one-time triggers — this improves performance and reduces callback spam. - Leverage
data-in-view-classfor CSS-only animations when possible — it's more performant than JavaScript-based transitions. - Use the
directionparameter in callbacks to create context-aware animations and interactions.