REACT COOL INVIEW
A React hook / component API that monitors an element enters or leaves the viewport (or another element) with highly-performant way, using Intersection Observer. It's lightweight and super flexible, which can cover all the cases that you need, like lazy-loading images and videos, infinite scroll web app, triggering animations, tracking impressions, and more. Try it you will ππ» it!
β€οΈ it? βοΈ it on GitHub or Tweet about it.
β‘οΈ Try yourself: https://react-cool-inview.netlify.app
Features
Requirement
To use react-cool-inview
, you must use react@16.8.0
or greater which includes hooks.
Installation
This package is distributed via npm.
$ yarn add react-cool-inview
$ npm install --save react-cool-inview
Usage
react-cool-inview
has a flexible API design, it can cover simple to complex use cases for you. Here are some ideas for how you can use it.
β οΈ Most modern browsers support Intersection Observer natively. You can also add polyfill for full browser support.
Basic usage
To monitor an element enters or leaves the viewport by the inView
state and useful sugar events.
import { useInView } from "react-cool-inview";
const App = () => {
const { observe, unobserve, inView, scrollDirection, entry } = useInView({
threshold: 0.25,
onChange: ({ inView, scrollDirection, entry, observe, unobserve }) => {
unobserve();
observe();
},
onEnter: ({ scrollDirection, entry, observe, unobserve }) => {
},
onLeave: ({ scrollDirection, entry, observe, unobserve }) => {
},
});
return <div ref={observe}>{inView ? "Hello, I am π€" : "Bye, I am π΄"}</div>;
};
π‘ You don't have to call unobserve
when the component is unmounted, this hook will handle it for you.
Using as a Component
Changes HelloText
when it enters the viewport. The options can be passed through the props.
import { InView } from "react-cool-inview";
const HelloText = ({ inView, observe }) => (
<div ref={observe}>{inView ? "Hello, I am π€" : "Bye, I am π΄"}</div>
);
const App = () => (
<InView unobserveOnEnter>
<HelloText />
</InView>
);
π‘ InView
passes observe
and other props to the HelloText
.
Lazy-loading Images
It's super easy to build an image lazy-loading component with react-cool-inview
to boost the performance of your web app.
import { useInView } from "react-cool-inview";
const LazyImage = ({ width, height, ...rest }) => {
const { observe, inView } = useInView({
unobserveOnEnter: true,
rootMargin: "50px",
});
return (
<div className="placeholder" style={{ width, height }} ref={observe}>
{inView && <img {...rest} />}
</div>
);
};
π‘ Looking for a comprehensive image component? Try react-cool-img, it's my other component library.
Infinite Scroll
Infinite scroll is a popular design technique like Facebook and Twitter feed etc., new content being loaded as you scroll down a page. The basic concept as below.
import { useState } from "react";
import { useInView } from "react-cool-inview";
import axios from "axios";
const App = () => {
const [todos, setTodos] = useState(["todo-1", "todo-2", "..."]);
const { observe } = useInView({
rootMargin: "50px 0px",
onEnter: ({ unobserve }) => {
unobserve();
axios.get("/todos").then((res) => {
setTodos([...todos, ...res.todos]);
});
},
});
return (
<div>
{todos.map((todo, idx) => (
<div ref={idx === todos.length - 1 ? observe : null}>{todo}</div>
))}
</div>
);
};
π‘ Compare to pagination, infinite scroll provides a seamless experience for users and itβs easy to see the appeal. But when it comes to render a large lists, performance will be a problem. But don't worry, react-cool-virtual can help you out!
Trigger Animations
Another great use case is to trigger CSS animations once they are visible to the users.
import { useInView } from "react-cool-inview";
const App = () => {
const { observe, inView } = useInView({
unobserveOnEnter: true,
rootMargin: "-100px 0px",
});
return (
<div className="container" ref={observe}>
<div className={inView ? "fade-in" : ""}>I'm a π</div>
</div>
);
};
Track Impressions
react-cool-inview
can also play as an impression tracker, helps you fire an analytic event when a user sees an element or advertisement.
import { useInView } from "react-cool-inview";
const App = () => {
const { observe } = useInView({
threshold: 1,
onEnter: ({ unobserve }) => {
unobserve();
someTrackingService.send("π is seen");
},
});
return <div ref={observe}>I'm a π</div>;
};
Scrolling Direction
react-cool-inview
not only monitors an element enters or leaves the viewport but also tells you its scroll direction by the scrollDirection
object. The object contains vertical (y-axios) and horizontal (x-axios) properties, they're calculated whenever the target element meets a threshold
. If there's no enough condition for calculating, the value of the properties will be undefined
. In addition, the value of the properties will sync with the scrolling direction of the viewport.
import { useInView } from "react-cool-inview";
const App = () => {
const {
observe,
inView,
scrollDirection: { vertical, horizontal },
} = useInView({
threshold: [0.2, 0.4, 0.6, 0.8, 1],
onChange: ({ scrollDirection }) => {
console.log("Scroll direction: ", scrollDirection.vertical);
},
});
return (
<div ref={observe}>
<div>{inView ? "Hello, I am π€" : "Bye, I am π΄"}</div>
<div>{`You're scrolling ${vertical === "up" ? "β¬οΈ" : "β¬οΈ"}`}</div>
</div>
);
};
If you jump to a section by the Element.scrollTop and encounter the wrong value of the scrollDirection
. You can use updatePosition
method to correct the behavior.
import { useEffect } from "react";
import { useInView } from "react-cool-inview";
const App = () => {
const { observe, scrollDirection, updatePosition } = useInView({
threshold: [0.2, 0.4, 0.6, 0.8, 1],
});
useEffect(() => {
window.scrollTo(0, 500);
updatePosition();
}, []);
return (
<div ref={observe}>
<div>{`You're scrolling ${
scrollDirection.vertical === "up" ? "β¬οΈ" : "β¬οΈ"
}`}</div>
</div>
);
};
Intersection Observer v2
The Intersection Observer v1 can perfectly tell you when an element is scrolled into the viewport, but it doesn't tell you whether the element is covered by something else on the page or whether the element has any visual effects applied to it (like transform
, opacity
, filter
etc.) that can make it invisible. The main concern that has surfaced is how this kind of knowledge could be helpful in preventing clickjacking and UI redress attacks (read this article to learn more).
If you want to track the click-through rate (CTR) or impression of an element, which is actually visible to a user, Intersection Observer v2 can be the savior. Which introduces a new boolean field named isVisible. A true
value guarantees that an element is visible on the page and has no visual effects applied on it. A false
value is just the opposite. The characteristic of the isVisible
is integrated with the inView
state and related events (like onEnter, onLeave etc.) to provide a better DX for you.
When using the v2, there're somethings we need to know:
To use Intersection Observer v2, we must set the trackVisibility
and delay
options.
import { useInView } from "react-cool-inview";
const App = () => {
const { observe, inView } = useInView({
trackVisibility: true,
delay: 100,
onEnter: () => {
},
onLeave: () => {
},
});
return <div ref={observe}>{inView ? "Hello, I am π€" : "Bye, I am π΄"}</div>;
};
How to Share A ref
?
You can share a ref
as follows:
import { useRef } from "react";
import { useInView } from "react-cool-inview";
const App = () => {
const ref = useRef();
const { observe } = useInView();
return (
<div
ref={(el) => {
observe(el); // Set the target element for monitoring
ref.current = el; // Share the element for other purposes
}}
/>
);
};
Working in TypeScript
This hook supports TypeScript, you can tell the hook what type of element you are going to observe through the generic type:
const App = () => {
const { observe } = useInView<HTMLDivElement>();
return <div ref={observe} />;
};
π‘ For more available types, please check it out.
API
const returnObj = useInView(options?: object);
Return object
It's returned with the following properties.
Key | Type | Default | Description |
---|
observe | function | | To set a target element for monitoring or re-start observing the current target element. |
unobserve | function | | To stop observing the current target element. |
inView | boolean | | The visible state of the target element. If it's true , the target element has become at least as visible as the threshold that was passed. If it's false , the target element is no longer as visible as the given threshold. Supports Intersection Observer v2. |
scrollDirection | object | | The scroll direction of the target element. Which contains vertical and horizontal properties. See scroll direction for more information. |
entry | object | | The IntersectionObserverEntry of the target element. Which may contain the isVisible property of the Intersection Observer v2, depends on the browser compatibility. |
updatePosition | function | | To update the current position of the target element for some cases. |
Parameter
The options
provides the following configurations and event callbacks for you.
Key | Type | Default | Description |
---|
root | HTMLElement | window | The element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null . |
rootMargin | string | 0px | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections. |
threshold | number | number[] | 0 | Indicates at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback to run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1]. |
trackVisibility | boolean | false | Indicates whether the intersection observer will track changes in a targetβs visibility. It's required when using Intersection Observer v2. |
delay | number | | Indicates the minimum delay in milliseconds between notifications from the intersection observer for a given target. It's required when using Intersection Observer v2. |
unobserveOnEnter | boolean | false | Stops observe once the target element intersects with the intersection observer's root. It's useful when you only want to trigger the hook once, e.g. scrolling to run animations. |
onChange | function | | It's invoked whenever the target element meets a threshold specified for the intersection observer. The callback receives an event object which the same with the return object of the hook. |
onEnter | function | | It's invoked when the target element enters the viewport. The callback receives an event object which the same with the return object of the hook except for inView . Supports Intersection Observer v2. |
onLeave | function | | It's invoked when the target element leaves the viewport. The callback receives an event object which the same with the return object of the hook except for inView . Supports Intersection Observer v2. |
rootMargin
Not Working As Expected?
If your web app is running in an <iframe>
or you have a custom root
, the viewport won't be the current document
. Read the doc to understand how do root and root margin work.
Intersection Observer Polyfill
Intersection Observer has good support amongst browsers, but it's not universal. You'll need to polyfill browsers that don't support it. Polyfills is something you should do consciously at the application level. Therefore react-cool-inview
doesn't include it.
You can use W3C's polyfill:
$ yarn add intersection-observer
$ npm install --save intersection-observer
Then import it at your app's entry point:
import "intersection-observer";
Or use dynamic imports to only load the file when the polyfill is required:
(async () => {
if (!("IntersectionObserver" in window))
await import("intersection-observer");
})();
Polyfill.io is an alternative way to add the polyfill when needed.
Performance Issues
Be aware that the callback of the onChange
event is executed on the main thread, it should operate as quickly as possible. If any time-consuming needs to be done, use requestIdleCallback or setTimeout.
onChange = (event) => requestIdleCallback(() => this.handleChange(event));
Articles / Blog Posts
π‘ If you have written any blog post or article about react-cool-inview
, please open a PR to add it here.
Contributors β¨
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!