React Logoreact-intersection-observer

react-intersection-observer is a powerful React hook and component library that provides an easy-to-use wrapper around the native Intersection Observer API. The Intersection Observer API is a browser API that allows you to asynchronously observe changes in the intersection of a target element with an ancestor scroll container or with the top-level document's viewport.

Why use react-intersection-observer?
It simplifies common use cases such as:
* Lazy Loading: Loading images, videos, or components only when they are about to enter the viewport, improving initial page load performance.
* Infinite Scrolling: Triggering the loading of more content when a user scrolls near the bottom of a list.
* Animation Triggers: Starting animations or visual effects when an element becomes visible.
* Tracking Element Visibility: Knowing when an element is in or out of view for analytics or other purposes.

How it works:
The library primarily exposes the `useInView` hook (for functional components) and an `<InView>` component (for class components or when render props are preferred). You attach a `ref` (provided by the hook/component) to the DOM element you want to observe. When that element's visibility changes relative to its root (viewport by default), a callback is triggered, and the `inView` state (a boolean indicating visibility) is updated.

Key Features & Props (primarily for `useInView` hook):
* `useInView()` hook: The most common way to use it in functional components. It returns an array `[ref, inView, entry]`.
* `ref`: A React ref that must be attached to the DOM element you want to observe.
* `inView`: A boolean indicating whether the observed element is currently intersecting with the root.
* `entry`: The `IntersectionObserverEntry` object, containing detailed information about the intersection change.
* `<InView>` component: Provides similar functionality using a render prop (`{({ inView, ref, entry }) => ...}`).
* `threshold`: A number or an array of numbers between 0 and 1, indicating the percentage of the target element's visibility at which the observer's callback should be executed.
* `0`: Trigger as soon as one pixel is visible.
* `1`: Trigger only when the entire element is visible.
* `[0, 0.25, 0.5, 0.75, 1]`: Trigger at these specific visibility percentages.
* `root`: The element that is used as the viewport for checking intersection. Defaults to the browser viewport.
* `rootMargin`: A margin around the `root` element, extending or shrinking its effective intersection area. Similar to CSS `margin` properties (e.g., "10px 20px 30px 40px").
* `triggerOnce`: If `true`, the observer will unobserve the target element once it has intersected the root at least once. Useful for one-shot animations or lazy loading.
* `onChange`: An optional callback function that is fired whenever the `inView` status changes, receiving `(inView, entry)` as arguments.

By abstracting away the complexities of the native API and integrating seamlessly with React's component lifecycle and state management, `react-intersection-observer` makes it significantly easier to build performant and responsive user interfaces.

Example Code

import React from 'react';
import { useInView } from 'react-intersection-observer';

// Basic styling (can be in App.css or inline)
const styles = {
  app: {
    fontFamily: 'sans-serif',
    textAlign: 'center',
    margin: 0,
    padding: 0,
  },
  spacer: {
    height: '100vh',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
  },
  placeholderBox: {
    height: '50vh',
    backgroundColor: '#e0e0e0',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    marginBottom: '20px',
  },
  imageContainer: {
    minHeight: '400px', // Prevent layout shift
    backgroundColor: '#f9f9f9',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    marginBottom: '20px',
  },
  skeletonLoader: {
    width: '600px',
    height: '400px',
    backgroundColor: '#cccccc',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    color: '#666',
    fontSize: '1.2em',
  },
  observedBox: {
    height: '300px',
    width: '80%',
    maxWidth: '600px',
    margin: '40px auto',
    backgroundColor: '#add8e6', // Light blue
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: '8px',
    boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
    opacity: 0,
    transform: 'translateY(20px)',
    transition: 'opacity 0.5s ease-out, transform 0.5s ease-out, background-color 0.5s ease-out',
  },
  observedBoxVisible: {
    opacity: 1,
    transform: 'translateY(0)',
    backgroundColor: '#90ee90', // Light green when visible
  },
  heading: {
    marginTop: '50px',
  },
};

function App() {
  // Hook for lazy loading an image
  const { ref: imageRef, inView: imageInView } = useInView({
    threshold: 0, // Trigger as soon as any part of the image is visible
    triggerOnce: true, // Only trigger once, then unobserve
  });

  // Hook for observing a general box with specific options
  const { ref: boxRef, inView: boxInView } = useInView({
    threshold: 0.5, // Trigger when 50% of the box is visible
    rootMargin: '0px 0px -100px 0px', // Shrink the root's bottom margin by 100px
  });

  return (
    <div style={styles.app}>
      <h1 style={styles.heading}>react-intersection-observer Example</h1>

      <div style={styles.spacer}>
        <h2>Scroll down to see the magic!</h2>
      </div>

      <div style={styles.placeholderBox}>
        <p>This is a placeholder before the observed image.</p>
      </div>

      <div ref={imageRef} style={styles.imageContainer}>
        {imageInView ? (
          <img
            src="https://via.placeholder.com/600x400?text=Lazy+Loaded+Image"
            alt="Lazy Loaded Content"
            style={{ maxWidth: '100%', height: 'auto' }}
          />
        ) : (
          <div style={styles.skeletonLoader}>
            Loading image...
          </div>
        )}
      </div>

      <div style={styles.placeholderBox}>
        <p>Another placeholder before the observed box.</p>
      </div>

      <div
        ref={boxRef}
        style={{
          ...styles.observedBox,
          ...(boxInView ? styles.observedBoxVisible : {}),
        }}
      >
        {boxInView ? (
          <p>
            Hello from the observed box! I am 50% in view.
            <br />
            <code>inView</code> is: {String(boxInView)}
          </p>
        ) : (
          <p>Scroll down to see this box appear!</p>
        )}
      </div>

      <div style={styles.spacer}>
        <h2>End of example.</h2>
      </div>
    </div>
  );
}

export default App;