Thursday, April 25, 2024
HomeRuby On RailsHow one can detect adjustments in element visibility when scrolling?

How one can detect adjustments in element visibility when scrolling?


When there’s a have to show massive set of knowledge, a lot of the net purposes
cut up entire set into a number of smaller chunks after which serve on demand. This
method is named pagination.

Earlier, pagination regarded like this:
image
Right here, loading subsequent set of knowledge required person to click on on subsequent web page button.

Today, we use infinite scroll method which mechanically hundreds subsequent set
of knowledge when person scrolls to the underside of the record. That is extra person pleasant:
image

A number of JS libraries can be found to facilitate infinite scroll. However to quench
our curiosity about how issues work underneath the hood, it’s best to attempt to
implement it from the scratch.

To implement infinite scroll, we have to know when the person has scrolled to the
backside of the record to load the following web page’s knowledge. To know if the person has reached
the underside, we will watch the final ingredient of the record. That’s, when the record is
scrolled and the final ingredient turns into seen, we all know that we’re on the
backside.

Detecting the visibility of components throughout scroll was a tough drawback till
lately. We needed to hook onto onscroll occasions of the ingredient and verify the
boundaries of the weather utilizing
getBoundingClientRect
perform.

Since onscroll occasion will get fired round 40-50 instances per second, performing the
operations inside it is going to turn into costly. Furthermore, the onscroll perform
will get executed from the primary UI thread. All these collectively make our net
software sluggish.

However now, we’ve a way more performant different for this drawback.
All widespread net browsers
assist a brand new API named
IntersectionObserver
from 2019 onwards.

The benefits of IntersectionObserver API are:

  • It would not seize the assets from the UI thread. It accepts a callback
    perform that might be fired asynchronously.
  • The provided callback is triggered solely when a change in visibility is
    detected. We will save 40-50 repetitions per second in the course of the scroll.
  • We needn’t fear about sustaining boilerplate code for detecting the
    boundaries & calculating the visibility. We get all helpful knowledge as a parameter
    to the callback perform.

The introduction of IntersectionObserver simplified an entire set of
necessities like:

  • Infinite loading.
  • Bettering web page load time by not fetching assets (like photographs) that are not
    seen till the person scrolls to it. That is known as lazy loading.
  • Monitor whether or not the person has scrolled to and seen an advert posted on the net
    web page.
  • UX enhancements like dimming animation for the elements that are not totally
    seen.

On this weblog, we’re going to talk about how we will use IntersectionObserver in a
React software as hooks.

Making a hook for detecting visibility adjustments

We are going to create a customized hook that can replace each time the required element
scrolls into view and scrolls out of view. Allow us to identify the hook
useIsElementVisible. Clearly, it is going to settle for a reference to the element of
which visibility should be monitored as its argument.

It would have a state to retailer the visibility standing of the required ingredient. It
could have a useEffect hook from which we’ll bind the IntersectionObserver to
the required element.

Right here is the essential implementation:

1import { useEffect, useState } from "react";
2
3const useIsElementVisible = goal => {
4  const [isVisible, setIsVisible] = useState(false); // retailer visibility standing
5
6  useEffect(() => {
7    // bind IntersectionObserver to the goal ingredient
8    const observer = new IntersectionObserver(onVisibilityChange);
9    observer.observe(goal);
10  }, [target]);
11
12  // deal with visibility adjustments
13  const onVisibilityChange = entries => setIsVisible(entries[0].isIntersecting);
14
15  return isVisible;
16};
17
18export default useIsElementVisible;

We will use useIsElementVisible like this:

1const ListItem = () => {
2  const elementRef = useRef(null); // to carry reference to the element we have to monitor
3
4  const isElementVisible = useIsElementVisible(elementRef.present);
5
6  return (
7    <div ref={elementRef} id="list-item">
8      {/* your element jsx */}
9    </div>
10  );
11};

The element ListItem will get up to date each time the person has scrolled to see
the div “list-item”. We will use the worth of isElementVisible to load the
contents of the following web page from a useEffect hook:

1useEffect(() => {
2  if (isElementVisible && nextPageNotLoadedYet()) {
3    loadNextPage();
4  }
5}, [isElementVisible]);

This works in principle. However when you strive it, you’ll discover that this does not
work as anticipated. We missed an edge case.

The actual-life edge case

We use a useRef hook for referencing the div. In the course of the preliminary render,
elementRef was simply initialized with null as its worth. So,
elementRef.present might be null and consequently, the decision
useIsElementVisible(elementRef.present) will not connect our observer with the
ingredient for the primary time.

Sadly, useRef hook will not re-render when a worth is about to it after DOM
is ready. Additionally, there aren’t any state updates or something that requests a
rerender inside our instance element. Briefly, our element will render solely
as soon as.

With these in place, useIsElementVisible won’t ever get reference to the
“list-item” div in our earlier instance.

However there’s a workaround for our drawback. We will power render the element
twice in the course of the first mount.

To make it potential, we’ll add a dummy state. When our hook is named for the
first time (when ListItem mounts), we’ll replace our state as soon as, thereby
requesting React to repeat the element render steps once more. In the course of the second
render, we’ll have already got our DOM prepared and we could have the goal ingredient
hooked up to elementRef.

Drive re-rendering the element

To maintain our code clear and modular, allow us to create a devoted customized hook for
managing power re-renders:

1import { useState } from "react";
2
3const useForceRerender = () => {
4  const [, setValue] = useState(0); // we do not want the worth of this state.
5  return () => setValue(worth => worth + 1);
6};
7
8export default useForceRerender;

Now, we will use it in our useIsElementVisible hook this manner:

1const useIsElementVisible = goal => {
2  const [isVisible, setIsVisible] = useState(false);
3
4  const forceRerender = useForceRerender();
5
6  useEffect(() => {
7    forceRerender();
8  }, []);
9
10  // earlier code to register observer
11
12  return isIntersecting;
13};

With this transformation, our hook is now self-sufficient and totally practical. In our
ListItem element, isElementVisible will replace to false and set off
element re-render each time our “list-item” div goes outdoors seen zone
throughout scroll. It would additionally replace to true when it’s scrolled into visibility
once more.

Doable enhancements on useIsElementVisible hook

The useIsElementVisible hook proven within the earlier sections serves solely the
fundamental use case. It’s not optimum to make use of in a manufacturing world.

These are the scopes for enchancment for our hook:

  • We will let in
    configurations for IntersectionObserver
    to customise its habits.
  • We will forestall initializing observer when the goal just isn’t prepared but (when
    it’s null).
  • We will add a cleanup perform to cease observing the ingredient when our element
    will get unmounted.

Here’s what the optimum code for the hook ought to appear to be:

1import { useEffect, useState } from "react";
2
3export const useForceRerender = () => {
4  const [, setValue] = useState(0); // we do not want the worth of this state.
5  return () => setValue(worth => worth + 1);
6};
7
8export const useIsElementVisible = (goal, choices = undefined) => {
9  const [isVisible, setIsVisible] = useState(false);
10  const forceUpdate = useForceRerender();
11
12  useEffect(() => {
13    forceUpdate(); // to make sure that ref.present is hooked up to the DOM ingredient
14  }, []);
15
16  useEffect(() => {
17    if (!goal) return;
18
19    const observer = new IntersectionObserver(handleVisibilityChange, choices);
20    observer.observe(goal);
21
22    return () => observer.unobserve(goal);
23  }, [target, options]);
24
25  const handleVisibilityChange = ([entry]) =>
26    setIsVisible(entry.isIntersecting);
27
28  return isVisible;
29};

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments