This tutorial is out of date and no longer maintained.
The most dreaded features when building frontend apps are features that need you to take control of the scroll events, behavior, and properties. Not only are they hard to implement because of numbers crunching, but more often they are prone to affect performance badly. A lot of us head to libraries for help but they still can’t help you with performance. This is because they use the main thread to manage the scroll events which are called frequently.
A more efficient and simpler alternative is the in-built Intersection Observer which is fairly new but can be polyfilled.
The Intersection Observer does not require an event. Rather, it waits for a rectangle you want to observe to get into view before running any code. You can call this rectangle the target and the view to be entered, the root.
From the image above, root can be the entire page or a portion (e.g., div) in the page. By default, observation starts once the target enters the root.
Here is a code example that shows how to achieve the intersection in the above diagram:
const options = {
root: document.querySelector('#divRoot'), /* or `null` for page as root */
}
const observer = new IntersectionObserver(callback, options);
After setting up the observer, you can start observing the target:
const target = document.querySelector('#divTarget');
observer.observe(target);
For each time an intersection happens, the callback function in the IntersectionObserver
constructor is called:
const callback = (entities, options) => {
console.log(entities, options)
}
The threshold refers to how much of an intersection has been observed. This illustration should help you understand better:
The threshold for page A is 25%. B is 50% while C is 75%. These are not just figures, you can tell your browser to start observing at any threshold. By default, the observations start at 0.0 but you can ignore the first half of the target and start observing at the second half (0.5).
To set the threshold, all that needs to be done is to add the threshold to the options
object:
const options = {
root: document.querySelector('#divRoot'), /* or `null` for page as root */
threshold: 1.0 // Only observe when the entire box is in view
}
Now let’s see how to use this in a real example.
In your React’s App
component, add the following constructor:
constructor(props) {
super(props);
this.state = {
users: [],
page: 0,
loading: false,
prevY: 0
};
}
The state object contains the following:
users
: Stores a list of users on GitHubpage
: The start page for the list of users from GitHubloading
: When true
shows a div with a loading text. This will be helpful when fetching dataprevY
: This is where the last intersection y position will be stored for referenceAdd axios
as dependency via npm then add a componentDidMount
in the App
class:
componentDidMount() {
this.getUsers(this.state.page);
}
The method is calling getUsers
which we will create next:
getUsers(page) {
this.setState({ loading: true });
axios
.get(`https://api.github.com/users?since=${page}&per_page=100`)
.then(res => {
this.setState({ users: [...this.state.users, ...res.data] });
this.setState({ loading: false });
});
}
This method takes a page parameter and uses the parameter to query the GitHub API for users. It then updates the state with the users. Notice how the method is flipping the loading
state to show a loading text.
Go ahead to render the list in the browser:
render() {
const loadingCSS = {
height: '100px',
margin: '30px'
};
const loadingTextCSS = { display: this.state.loading ? 'block' : 'none' };
return (
<div className="container">
<div style={{ minHeight: '800px' }}>
<ul>
{this.state.users.map(user => <li key={user.id}>{user.login}</li>)}
</ul>
</div>
<div
ref={loadingRef => (this.loadingRef = loadingRef)}
style={loadingCSS}
>
<span style={loadingTextCSS}>Loading...</span>
</div>
</div>
);
}
Notice loadingRef
div which shows a loading text. We’ll use it later as a target for our Intersection Observer.
We want the observer to start after a component is mounted, therefore we should set it up in componentDidMount
:
componentDidMount() {
this.getUsers(this.state.page);
// Options
var options = {
root: null, // Page as root
rootMargin: '0px',
threshold: 1.0
};
// Create an observer
this.observer = new IntersectionObserver(
this.handleObserver.bind(this), //callback
options
);
//Observ the `loadingRef`
this.observer.observe(this.loadingRef);
}
Just as we saw earlier, this.observer
is the instance of IntersectionObserver
. We are also using the instance to observe the loadingRef
we created in the render
method above. This makes loadingRef
the target.
The callback is named handleObserver
. Let’s create it like so:
handleObserver(entities, observer) {
const y = entities[0].boundingClientRect.y;
if (this.state.prevY > y) {
const lastUser = this.state.users[this.state.users.length - 1];
const curPage = lastUser.id;
this.getUsers(curPage);
this.setState({ page: curPage });
}
this.setState({ prevY: y });
}
The method completes each of the following:
lastUser
and curePage
variables help to maintain this.curPage
has been updated, we can then call getUsers
with its value and update the state with setState
.Have another look at the example:
It’s important to mention that as much as this is exciting to use, it’s not ready and it’s yet to be supported well enough across browsers. Keep this in mind and use a polyfill.
You can refer to this to learn more about support.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.