Bring ReactIntersectionObserver over today, your React children will love it!
React Intersection Observer is a React component, acting as a wrapper for the IntersectionObserver API. It is fully declarative and takes care of all the imperative parts for you.
React Intersection Observer is good at:
- reusing instances: comparing the passed options
- performance: chooses smartly when to re-render and when to re-observe
- being unopinionated: how to handle visibility changes is left entirely up to the developer
- being intuitive: looks like the Native API
- small size: ~6.5kB before minification (> 3.5kB or less when minified, ~1.3kB gzipped)
Table of Contents
npm install --save @researchgate/react-intersection-observer
Usage:
import React from 'react';
import 'intersection-observer'; // optional polyfill
import Observer from '@researchgate/react-intersection-observer';
class ExampleComponent extends React.Component {
handleIntersection(event) {
console.log(event.isIntersecting);
}
render() {
const options = {
onChange: this.handleIntersection,
root: '#scrolling-container',
rootMargin: '0% 0% -25%',
};
return (
<div id="scrolling-container" style={{ overflow: 'scroll', height: 100 }}>
<Observer {...options}>
<div>I am the target element</div>
</Observer>
</div>
);
}
}
Optionally add the polyfill and make sure it's required on your dependendencies for unsupporting browsers:
npm install --save intersection-observer
IntersectionObservers calculate how much of a target element overlaps (or "intersects with") the visible portion of a page, also known as the browser's "viewport":
The motivation is to provide the easiest possible solution for observing elements that enter the viewport on your React codebase. It's fully declarative and all complexity is abstracted away, focusing on reusability, and low memory consumption.
It's built with compatibility in mind, adhering 100% to the native API implementation and DSL, but takes care of all the bookkeeping work for you.
Instances and nodes are managed internally so that any changes to the passed options or tree root reconciliation cleans up and re-observes nodes on-demand to avoid any unexpected memory leaks.
ReactIntersectionObserver does not create any extra DOM elements, it attaches to the only child you'll provide to it.
Internally it warns you if attaching a ref
to it fails - common mistake when using a stateless component in React 15 -
and will invoke any existing ref
callback upon the passed child element.
When using ReactIntersectionObserver the only required prop is the onChange
function. Any changes to the visibility of
the element will invoke this callback, just like in the
native API -
you’ll receive one IntersectionObserverEntry
argument per change. This gives you an ideal and flexible base to build
upon.
Some of the things you may want to use ReactIntersectionObserver for:
- Determining advertisement impressions
- Lazy loading - Images, or anything that will enter the viewport
- Occlusion culling - Don't render an object until is close to the viewport edges
- Sentinel Scrolling - Infinite scroller with a recycled Sentinel
Find multiple examples and usage guidelines under: https://researchgate.github.io/react-intersection-observer/
Recipes are useful code snippets solutions to common problems, for example, how to use ReactIntersectionObserver within
a
Higher Order Component.
Here's how to create an element monitoring component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Observer from '@researchgate/react-intersection-observer';
export default class ViewableMonitor extends Component {
static propTypes = {
tag: PropTypes.node,
children: PropTypes.func.isRequired,
}
static defaultProps = {
tag: 'div',
}
state = {
isIntersecting: false,
}
handleChange = ({ isIntersecting }) => {
this.setState({ isIntersecting });
};
render() {
const { tag: Tag, children, ...rest } = this.props;
return (
<Observer {...rest} onChange={this.handleChange}>
<Tag>
{children(this.state.isIntersecting)}
</Tag>
</Observer>
);
}
}
import React from 'react';
import ViewableMonitor from './ViewableMonitor';
export default () => (
<ViewableMonitor>
{isViewable =>
isViewable ? 'I am viewable' : 'I am still hiding'
}
</ViewableMonitor>
);
Discover more recipes in our examples section.
root: HTMLElement|string
| default window object
The element or selector string that is used as the viewport for checking visibility of the target.
rootMargin: string
| default 0px 0px 0px 0px
Margin around the root. Specify using units px or % (top, right, bottom left). Can contain negative values.
threshold: number|Array<number>
| default: 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 run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1].
disabled: boolean
| default: false
Controls whether the element should stop being observed by its IntersectionObserver instance. Useful for temporarily disabling the observing mechanism and restoring it later.
onChange (required): (entry: IntersectionObserverEntry, unobserve: () => void) => void
Function that will be invoked whenever an observer's callback contains this target in its changes.
children (required): React.Element<*>
Single React component or element that is used as the target (observable).
- According to the spec, an initial event is being fired when starting to observe a non-intersecting element as well.
- Edge's implementation seems to miss the initial event, although Edge 16 behavior aligns with the spec.
- Changes happen asynchronously, similar to the way
requestIdleCallback
works. - Although you can consider callbacks immediate - always below 1 second - you can also get an immediate response on an
element's visibility with
observer.takeRecords()
. - The primitives
Map
anSet
are required. You may need to include a polyfill for browsers lacking ES2015 support. If you're using babel, include"babel-polyfill"
somewhere to your codebase.
When needing the full spec's support, we highly recommend using the IntersectionObserver polyfill.
Earlier preview versions of Edge and
prior to version 58 of Chrome, the support for
isIntersecting
was lacking. This property was added to the spec later and both teams where unable to implement it
earlier.
As the above-mentioned polyfill doesn't perform callback invocation
asynchronously, you might want to decorate your onChange
callback with a requestIdleCallback
or setTimeout
call to avoid a potential performance degradation:
onChange = entry => requestIdleCallback(() => this.handleChange(entry));
Chrome | 51 [1] |
Firefox (Gecko) | 55 [2] |
MS Edge | 15 |
Internet Explorer | Not supported |
Opera [1] | 38 |
Safari | Safari Technology Preview |
Chrome for Android | 59 |
Android Browser | 56 |
Opera Mobile | 37 |
- [1]reportedly available, it didn't trigger the events on
initial load and lacks
isIntersecting
until later versions. - [2] This feature was implemented in Gecko 53.0 (Firefox 53.0 / Thunderbird 53.0 / SeaMonkey 2.50) behind the
preference
dom.IntersectionObserver.enabled
.
Safari | 6+ |
Internet Explorer | 7+ |
Android | 4.4+ |
We'd love your help on creating React Intersection Observer!
Before you do, please read our Code of Conduct so you know what we expect when you contribute to our projects.
Our Contributing Guide tells you about our development process and what we're looking for, gives you instructions on how to issue bugs and suggest features, and explains how you can build and test your changes.
Haven't contributed to an open source project before? No problem! Contributing Guide has you covered as well.