react-intersection-observer
React implementation of the Intersection Observer API to tell you when an e...
README
react-intersection-observer
[![Version Badge][npm-version-svg]][package-url] [![GZipped size][npm-minzip-svg]][bundlephobia-url] [![Test][test-image]][test-url] [![License][license-image]][license-url] [![Downloads][downloads-image]][downloads-url]
React implementation of the
to tell you when an element enters or leaves the viewport. Contains both a
Hooks, render props and
plain children implementation.
Storybook Demo:
Features
- 🪝 Hooks or Component API - With useInView it's easier than ever to
monitor elements
- ⚡️ Optimized performance - Reuses Intersection Observer instances where
possible
- ⚙️ Matches native API - Intuitive to use
- 🛠 Written in TypeScript - It'll fit right into your existing TypeScript
project
- 🧪 Ready to test - Mocks the Intersection Observer for easy testing with
- 🌳 Tree-shakeable - Only include the parts you use
- 💥 Tiny bundle - Around ~1.15kB for useInView and ~1.6kB for
`Installation
Install using Yarn:
- ```sh
- yarn add react-intersection-observer
- ```
or NPM:
- ```sh
- npm install react-intersection-observer --save
- ```
Usage
useInView hook
- ``` js
- // Use object destructing, so you don't need to remember the exact order
- const { ref, inView, entry } = useInView(options);
- // Or array destructing, making it easy to customize the field names
- const [ref, inView, entry] = useInView(options);
- ```
The useInView hook makes it easy to monitor the inView state of your
components. Call the useInView hook with the (optional) options
you need. It will return an array containing a ref, the inView status and
the current
[entry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).
Assign the ref to the DOM element you want to monitor, and the hook will
report the status.
- ``` js
- import React from 'react';
- import { useInView } from 'react-intersection-observer';
- const Component = () => {
- const { ref, inView, entry } = useInView({
- /* Optional options */
- threshold: 0,
- });
- return (
- <div ref={ref}>
- <h2>{`Header inside viewport ${inView}.`}</h2>
- </div>
- );
- };
- ```
Render props
whenever the state changes, with the new value of inView. In addition to the
inView prop, children also receive a ref that should be set on the
containing DOM element. This is the element that the Intersection Observer will
monitor.
If you need it, you can also access the
[IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)
on entry, giving you access to all the details about the current intersection
state.
- ``` js
- import { InView } from 'react-intersection-observer';
- const Component = () => (
- <InView>
- {({ inView, ref, entry }) => (
- <div ref={ref}>
- <h2>{`Header inside viewport ${inView}.`}</h2>
- </div>
- )}
- </InView>
- );
- export default Component;
- ```
Plain children
wrapping DOM element. Add a handler to the onChange method, and control the
state in your own component. Any extra props you add to `passed to the HTML element, allowing you set the className, style, etc.
- ``` js
- import { InView } from 'react-intersection-observer';
- const Component = () => (
- <InView as="div" onChange={(inView, entry) => console.log('Inview:', inView)}>
- <h2>Plain children are always rendered. Use onChange to monitor state.</h2>
- </InView>
- );
- export default Component;
- ```
Note<br>
When rendering a plain child, make sure you keep your HTML output semantic.
Change the as to match the context, and add a className to style the
<InView />. The component does not support Ref Forwarding, so if you need a
ref to the HTML element, use the Render Props version instead.
API
Options
Provide these as the options argument in the useInView hook or as props on the
**`Name | Type | Default | Description |
---|---|---|---|
---------------------- | ------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
**root** | `Element` | `document` | The |
**rootMargin** | `string` | `'0px'` | Margin |
**threshold** | `number` | `0` | Number |
**onChange** | `(inView, | `undefined` | Call |
**trackVisibility** | `boolean` | `false` | A |
**delay** | `number` | `undefined` | A |
**skip** | `boolean` | `false` | Skip |
**triggerOnce** | `boolean` | `false` | Only |
**initialInView** | `boolean` | `false` | Set |
**fallbackInView** | `boolean` | `undefined` | If |
InView Props
Name | Type | Default | Description |
---|---|---|---|
------------ | ---------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
**as** | `string` | `'div'` | Render |
**children** | `({ref, | `undefined` | Children |
Intersection Observer v2 🧪
The new
extends the original API, so you can track if the element is covered by another
element or has filters applied to it. Useful for blocking clickjacking attempts
or tracking ad exposure.
To use it, you'll need to add the new trackVisibility and delay options.
When you get the entry back, you can then monitor if isVisible is true.
- ``` js
- const TrackVisible = () => {
- const { ref, entry } = useInView({ trackVisibility: true, delay: 100 });
- return <div ref={ref}>{entry?.isVisible}</div>;
- };
- ```
This is still a very new addition, so check
caniuse for current browser
support. If trackVisibility has been set, and the current browser doesn't
support it, a fallback has been added to always report isVisible as true.
It's not added to the TypeScript lib.d.ts file yet, so you will also have to
extend the IntersectionObserverEntry with the isVisible boolean.
Recipes
The IntersectionObserver itself is just a simple but powerful tool. Here's a
few ideas for how you can use it.
- Track impressions _(Google Analytics, Tag
Manager, etc)_
FAQ
How can I assign multiple refs to a component?
You can wrap multiple ref assignments in a single useCallback:
- ``` js
- import React, { useRef, useCallback } from 'react';
- import { useInView } from 'react-intersection-observer';
- function Component(props) {
- const ref = useRef();
- const { ref: inViewRef, inView } = useInView();
- // Use `useCallback` so we don't recreate the function on each render
- const setRefs = useCallback(
- (node) => {
- // Ref's from useRef needs to have the node assigned to `current`
- ref.current = node;
- // Callback refs, like the one from `useInView`, is a function that takes the node as an argument
- inViewRef(node);
- },
- [inViewRef],
- );
- return <div ref={setRefs}>Shared ref is visible: {inView}</div>;
- }
- ```
rootMargin isn't working as expected
When using rootMargin, the margin gets added to the current root - If your
application is running inside a `