Package Exports
- react-cool-inview
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (react-cool-inview) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
π§ This package is in-progress, API may be changed frequently. I don't recommend you to use it now. If you're willing to be an early user. Please note any changes via release. Here's the milestone.
React Cool Inview
A React hook that monitors an element enters or leaves the viewport (or another element) with performant and efficient way, using Intersection Observer. It's lightweight and super flexible, which can help you do many things, like lazy-loading images and videos, infinite scrolling web app, triggering animations, tracking impressions etc. Try it you will ππ» it!
Milestone
- Detect an element is in-view or not
-
onEnter
,onLeave
,onChange
events - Trigger once feature
- Server-side rendering compatibility
- Support Intersection Observer v2
- Unit testing
- Demo app
- Demo code
- Typescript type definition
- Documentation
Features
Coming soon...
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
# or
$ 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 Use Case
To monitor an element enters or leaves the browser viewport by the inView
state and useful sugar events.
import React, { useRef } from 'react';
import useInView from 'react-cool-inview';
const App = () => {
const ref = useRef();
const { inView, entry } = useInView(ref, {
threshold: 0.25, // Default is 0
onEnter: ({ entry, observe, unobserve }) => {
// Triggered when the target enters the browser viewport (start intersecting)
},
onLeave: ({ entry, observe, unobserve }) => {
// Triggered when the target leaves the browser viewport (end intersecting)
},
// More useful options...
});
return <div ref={ref}>{inView ? 'Hello, I am π€' : 'Bye, I am π΄'}</div>;
};
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 React, { useRef } from 'react';
import useInView from 'react-cool-inview';
const LazyImage = ({ width, height, ...rest }) => {
const ref = useRef();
const { inView } = useInView(ref, {
// Stop observe when meet the threshold, so the "inView" only triggered once
unobserveOnEnter: true,
// For better UX, we can grow the root margin so the image will be loaded before it comes to the viewport
rootMargin: '50px',
});
return (
<div className="placeholder" style={{ width, height }} ref={ref}>
{inView && <img {...rest} />}
</div>
);
};
π‘ Looking for a comprehensive image component? Try react-cool-img, it's my other component library.
Infinite Scrolling
Infinite scrolling 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 React, { useRef, useState } from 'react';
import useInView from 'react-cool-inview';
import axios from 'axios';
const App = () => {
const [todos, setTodos] = useState(['todo-1', 'todo-2', '...']);
const ref = useRef();
useInView(ref, {
// For better UX, we can grow the root margin so the data will be loaded before a user sees the loading indicator
rootMargin: '50px 0',
// When the loading indicator comes to the viewport
onEnter: ({ unobserve, observe }) => {
// Pause observe when loading data
unobserve();
// Load more data
axios.get('/todos').then((res) => {
setTodos([...todos, ...res.todos]);
// Resume observe after loading data
observe();
});
},
});
return (
<div>
{todos.map((todo) => (
<div>{todo}</div>
))}
<div ref={ref}>Loading...</div>
</div>
);
};
Compare to pagination, infinite scrolling 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. We can use react-window to address the problem by the technique of DOM recycling.
import React, { useRef, useState } from 'react';
import useInView from 'react-cool-inview';
import { FixedSizeList as List } from 'react-window';
import axios from 'axios';
const Row = ({ index, data, style }) => {
const { todos, handleLoadingInView } = data;
const isLast = index === todos.length;
const ref = useRef();
useInView(ref, { onEnter: handleLoadingInView });
return (
<div style={style} ref={isLast ? ref : null}>
{isLast ? 'Loading...' : todos[index]}
</div>
);
};
const App = () => {
const [todos, setTodos] = useState(['todo-1', 'todo-2', '...']);
const [isFetching, setIsFetching] = useState(false);
const handleLoadingInView = () => {
// Row component is dynamically created by react-window, we need to use the "isFetching" flag
// instead of unobserve/observe to avoid re-fetching data
if (!isFetching)
axios.get('/todos').then((res) => {
setTodos([...todos, ...res.todos]);
setIsFetching(false);
});
setIsFetching(true);
},
// Leverage the power of react-window to help us address the performance bottleneck
return (
<List
height={150}
itemCount={todos.length + 1} // Last one is for the loading indicator
itemSize={35}
width={300}
itemData={{ todos, handleLoadingInView }}
>
{Row}
</List>
);
};
Trigger Animations
Another great use case is to trigger CSS animations once they are visible to the users.
import React, { useRef } from 'react';
import useInView from 'react-cool-inview';
const App = () => {
const ref = useRef();
const { inView } = useInView(ref, {
// Stop observe when meet the threshold, so the "inView" only triggered once
unobserveOnEnter: true,
// Shrink the root margin, so the animation will be triggered once an element reach a fixed amount of visible
rootMargin: '-100px 0',
});
return (
<div className="container" ref={ref}>
<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 React, { useRef } from 'react';
import useInView from 'react-cool-inview';
const App = () => {
const ref = useRef();
useInView(ref, {
// For an element to be considered "seen", we'll say it must be 100% in the viewport
threshold: 1,
onEnter: ({ unobserve }) => {
// Stop observe when meet the threshold, so the "onEnter" only triggered once
unobserve();
// Fire an analytic event to your tracking service
someTrackingService.send('π is seen');
},
});
return <div ref={ref}>I'm a π</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 on 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. When using the v2, there're something we need to know:
- Check browser compatibility, if a browser doesn't support Intersection Observer v2 the
isVisible
will beundefined
. - Understand how visibility is calculated.
- Visibility is much more expensive to compute than intersection, only use it when needed.
To enable Intersection Observer v2, we need to set the trackVisibility
and delay
options.
import React, { useRef } from 'react';
import useInView from 'react-cool-inview';
const App = () => {
const ref = useRef();
const { inView, isVisible } = useInView(ref, {
// Track the actual visibility of the element
trackVisibility: true,
// Set a minimum delay between notifications, it must be set to 100 (ms) or greater
// For performance perspective, use the largest tolerable value as much as possible
delay: 100,
});
// If the browser doesn't support Intersection Observer v2, falling back to v1 behavior
const visible = isVisible || inView;
return <div ref={ref}>{visible ? 'Hello, I am π€' : 'Bye, I am π΄'}</div>;
};
API
Coming soon...
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
# or
$ npm install --save intersection-observer
Then import it at your app's entry point:
import 'intersection-observer';
Or load the polyfill only if needed:
if (!window.IntersectionObserver) require('intersection-observer');
Polyfill.io is an alternative way to add the polyfill when needed.
Contributors β¨
Thanks goes to these wonderful people (emoji key):
Welly π» π π§ |
This project follows the all-contributors specification. Contributions of any kind welcome!