useInView
useInView(target: Element, init?: IntersectionObserverInit): Service<InViewProps>An IntersectionObserver as a service.
Props
interface InViewProps {
readonly isInView: boolean;
readonly entry: IntersectionObserverEntry | null;
}Usage
import { Base, useInView } from '@studiometa/js-toolkit';
class Reveal extends Base {
static config = { name: 'Reveal' };
mounted() {
return useInView(this.$el, { threshold: 0.25 }).subscribe(({ isInView }) => {
this.$el.classList.toggle('is-in-view', isInView);
});
}
}Keying
useInView() keys every IntersectionObserverInit field, and gives an object root a stable weak identity so two callers passing the same root element share one service.
The options are read by meaning, not by spelling: keys are sorted at every depth and the ones holding undefined are dropped, so { threshold: 0.5, rootMargin: '0px' } and { rootMargin: '0px', threshold: 0.5 } are the same service.
Nothing groups observers across targets.
{ immediate: true } is the default here
withInView defaults it to true, because "am I in view" has a current answer and a consumer that has to wait for the first crossing to find out would be wrong on load.
This is not a mount strategy
useInView and withInView observe a component that is already mounted. They do not replace data-mount="visible" or data-mount="in-view", which decide whether the instance exists at all.
| Want | Use |
|---|---|
| the component to exist only once seen | data-mount="visible" |
| the component to come and go with the viewport | data-mount="in-view" |
| a mounted component to react to crossings | useInView() / withInView |
See Mount strategies.
Mixin
class Reveal extends withInView(Base, { threshold: 0.25 }) {
intersected({ isInView }) {}
}