Million Miles Technologies

Control Lazy Load, Infinite Scroll and Animations in React — SitePoint


In this article, we’ll explore three use cases for working with the React Intersection Observer: lazy loading, infinite scrolling, and animation/transition triggers.

The React Intersection Observer aids in monitoring changes in an element’s viewport within your application. The Intersection Observer detects when a specific element intersects with another. It works by setting the callback function to execute whenever the target element interacts with the viewport.

It provides an elegant solution for handling element visibility and aiding you in creating dynamic applications.

Table of Contents

Use Case 1: Lazy Loading for Improved Performance

Lazy loading is a technique to defer the rendering of less critical components (data, code, images, videos), mostly in cases where loading everything from the start is detrimental to performance and leads to a decline in core web vitals. It can significantly improve page loading times, especially for content-heavy websites.

Simply put, lazy loading delays the loading of resources until they become visible in the viewport. Images below the fold or hidden within scrollable content won’t download immediately, preventing unnecessary resource consumption and page load delays.

You can lazy load without using the Intersection Observer, but the traditional onScroll event of the element doesn’t provide granular control over loading behavior. But the Intersection Observer allows you to observe a DOM element and trigger actions based on its intersection with the viewport.

Benefits of lazy loading with React Intersection Observer

  • Faster page load. By deferring image loading, initial page load times improve, leading to better user engagement and reducing initial page load time.
  • Improved core web vitals. Metrics like largest contentful paint (LCP) benefit from lazy loading, boosting your app’s SEO and Google Lighthouse scores.
  • Reduced data usage. Users on slower connections or limited data plans benefit from reduced data usage as images load on demand.
  • Smoother scrolling. Scrolling feels more responsive, as heavy images don’t download upfront, preventing janky page jumps.
  • Improved user experience. Users see the content they are interested in faster, leading to a better overall experience.

How to implement lazy loading with React Intersection Observer

To get started with our coding, we first need to set up React Intersection Observer in our React application. You can install the dependency using the following:

npm install react-intersection-observer

Import the necessary components and hooks:


import React from "react";
import { useInView } from "react-intersection-observer";
const LazyImage = ({ src, alt, className }) => {
  const [ref, inView] = useInView({
    triggerOnce: true, 
  });
  return (
    <img
      ref={ref}
      src={inView ? src : ""}
      alt={alt}
      className={`lazy-image ${className || ""}`}
    />
  );
};
export default LazyImage;

Integrating lazy loading into your components

Assume you have a list of images you want to lazy load. Now, let’s use the LazyImage component in our application:


import React from "react";
import LazyImage from "./LazyImage";
const ImageList = () => {
  const images = [
    { id: 1, src: "image1.jpg", alt: "Description 1" },
    { id: 2, src: "image2.jpg", alt: "Description 2" },
    { id: 3, src: "image3.jpg", alt: "Description 3" },
    { id: 4, src: "image4.png", alt: "Description 4" },
    { id: 5, src: "image5.png", alt: "Description 5" },
    { id: 6, src: "image6.png", alt: "Description 6" },
    { id: 7, src: "image7.png", alt: "Description 7" },
    { id: 8, src: "image8.png", alt: "Description 8" },
    { id: 9, src: "image9.png", alt: "Description 9" },
  ];
  return (
    <div>
      {images.map((image) => (
        <LazyImage
          key={image.id}
          src={image.src}
          alt={image.alt}
          className="lazy-image"
        />
      ))}
    div>
  );
};
export default ImageList;

The LazyImage component uses the useInView hook to determine if an element is in the viewport, utilizing the triggerOnce option for a single trigger. It dynamically sets the src attribute of an tag based on the element’s visibility. The ImageList component maps over a list of images, rendering a LazyImage for each one.

The following lazy loaded CodeSandbox contains the complete working code.

Implementing lazy loading for components with React Intersection Observer is a straightforward way to boost the performance of your React applications. Deferring the loading of resources until needed provides a faster and more efficient user experience. Consider adding lazy loading into your projects, especially if they involve substantial content.

Infinite scrolling is a technique that enhances user experience by loading additional content as the user reaches the bottom of a page.

Instead of requiring users to navigate through traditional pagination links, infinite scrolling provides a seamless and continuous flow of content.

By dynamically loading content as the user reaches the bottom of the page, websites keep users engaged and avoid disruptive page transitions.

Benefits of infinite scrolling with React Intersection Observer

  • Improved user experience. Users can seamlessly scroll through content without interruptions, providing a more engaging experience.
  • Efficient resource usage. Resources are only loaded when needed, reducing unnecessary data fetching and rendering.
  • Simplified navigation. Removes the need for traditional pagination links, simplifying the navigation experience for users.
  • Improved performance. Fine-grained observation avoids unnecessary calculations and content loading.
  • Increased visibility. Metrics like Time to Interactive benefit, boosting SEO and Google Lighthouse scores.

How to implement infinite scrolling with React Intersection Observer

As usual, add the React Intersection Observer to your React application:

npm install react-intersection-observer

Import the necessary components and hooks:


import React from "react";
import { useInView } from "react-intersection-observer";
const InfiniteScroll = ({ loadMore }) => {
  const [ref, inView] = useInView({
    triggerOnce: true,
  });
  React.useEffect(() => {
    if (inView) {
      loadMore();
    }
  }, [inView, loadMore]);
  return <div ref={ref} style={{ height: "10px" }} />;
};
export default InfiniteScroll;

Use the InfiniteScroll component to implement infinite scrolling:


import React, { useState } from "react";
import InfiniteScroll from "./InfiniteScroll";
const initialItems = [
  { id: 1, content: "Item 1" },
  { id: 2, content: "Item 2" },
  { id: 3, content: "Item 3" },
];
const fetchMoreData = (page) => {
  
  return Array.from({ length: 5 }, (_, index) => ({
    id: initialItems.length + index + 1,
    content: `Item ${initialItems.length + index + 1}`,
  }));
};
const InfiniteScrollList = () => {
  const [items, setItems] = useState(initialItems);
  const [page, setPage] = useState(1);
  const loadMore = () => {
    
    const newData = fetchMoreData(page + 1);
    setItems([...items, ...newData]);
    setPage(page + 1);
  };
  return (
    <div>
      {items.map((item) => (
        <div key={item.id} className="list-item">
          {}
          {item.content}
        div>
      ))}
      <InfiniteScroll loadMore={loadMore} />
    div>
  );
};
export default InfiniteScrollList;

The InfiniteScroll component uses the useInView hook to detect its visibility, invoking the loadMore function to load additional content. The loadMore function then fetches extra data, appending it to the existing list. The InfiniteScrollList component renders a continuous scroll experience, displaying the list of items with dynamic loading triggered by the InfiniteScroll component.

This Infinite Scroll CodeSandbox contains the complete working code.

Harnessing the power of the Intersection Observer builds you an efficient and engaging infinite scrolling experience. This technique boosts performance, user satisfaction, and your website’s overall appeal. Remember, optimizing your scroll journey helps keep users hooked to your application.

Use Case 3: Animation and Transition Triggers with the Intersection Observer

Animations and transitions bring life to a web page, making it more engaging for users. However, triggering these effects at the right moment can be challenging. The Intersection Observer offers a solution by allowing you to define when an element is in view, providing an excellent mechanism for triggering animations and transitions.

Gone are the days of animations firing on page load or at fixed scroll positions. The Intersection Observer unlocks a new level of dynamism.

Benefits of animations and transitions with the Intersection Observer

  • Performance boost. Animations only trigger when needed or when in view, saving resources and preventing unnecessary calculations.
  • Enhanced storytelling. Content comes alive as elements animate at the crucial moment, drawing user attention and emphasizing key points.
  • Responsive interactions. Different animations based on scroll depth allow for layered storytelling and personalized experiences.
  • Smooth scrolling. Transitions between states occur seamlessly, enhancing user engagement and preventing jarring interruptions.
  • Precise timing. Animation and transition triggers are based on the element’s visibility, ensuring precise timing and synchronization with user interactions.
  • Enhanced user engagement. Creating visually appealing and interactive components increases user engagement and provides a more dynamic user experience.

How to implement animations and transitions with React Intersection Observer

Again, install the React Intersection Observer dependency:

npm install react-intersection-observer

Create the reusable component for handling intersection events:


import React, { useEffect } from "react";
const IntersectionAnimationTrigger = ({ children, onInView }) => {
  const handleIntersection = (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        onInView();
      }
    });
  };
  useEffect(() => {
    const observer = new IntersectionObserver(handleIntersection, {
      threshold: 0.5,
    });
    
    observer.observe(document.getElementById("animated-element"));
    
    return () => {
      observer.disconnect();
    };
  }, [onInView]);
  return <div id="animated-element">{children}div>;
};
export default IntersectionAnimationTrigger;

Use the IntersectionAnimationTrigger component to animate when the element comes into view:


import React, { useState } from "react";
import IntersectionAnimationTrigger from "./IntersectionAnimationTrigger";
const AnimatedElement = () => {
  const [animated, setAnimated] = useState(false);
  const handleInView = () => {
    setAnimated(true);
  };
  return (
    <IntersectionAnimationTrigger onInView={handleInView}>
      <div className={`animated-element ${animated ? "animate" : ""}`}>
        {}
        This element will animate as it comes into view.
      div>
    IntersectionAnimationTrigger>
  );
};
export default AnimatedElement;

Add some CSS to make the animation visually appealing:


.animated-element {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
}
.animated-element.animate {
  opacity: 1;
  transform: translateY(0);
  width: 15rem;
  height: 5rem;
  margin: 0 auto;
  color: blue;
}

The IntersectionAnimationTrigger component uses the useInView hook to track element visibility, executing the onInView callback when it enters the view. Within the AnimatedElement component, the animated state toggles to initiate the animation when the element becomes visible.

This Animations and Transitions Codesandbox contains the complete working code.

For any application purpose, leveraging intersection events enhances the visual appeal of your project. Experiment with different animations and transitions to find the perfect balance for your personal use case.

Summary

React Intersection Observer simplifies the process of lazy loading, infinite scrolling, and animation triggering by providing React-specific hooks and components, making it easier for you to incorporate intersection-related functionality into your React applications. It abstracts away some of the complexities, offering a more declarative syntax that aligns with the component-based nature of React.

If you enjoyed this React article, check out these other great resources from SitePoint:

Further references

Related blogs