Convert Figma logo to code with AI

wellyshen logoreact-cool-inview

😎 🖥️ React hook to monitor an element enters or leaves the viewport (or another element).

1,464
41
1,464
18

Top Related Projects

23,338

Open source, production-ready animation and gesture library for React

React component for the Intersection <Observer /> API

Sensor component for React that notifies you when it goes in or out of the window viewport.

A React component to execute a function whenever you scroll to an element.

React components for efficiently rendering large lists and tabular data

Quick Overview

React Cool Inview is a React hook that provides an easy way to detect when an element enters or leaves the viewport. It leverages the Intersection Observer API for efficient tracking and offers a range of customizable options for fine-tuning the detection behavior.

Pros

  • Easy to use with a simple hook-based API
  • Highly customizable with various options
  • Supports both entering and leaving viewport events
  • Lightweight and performant due to use of Intersection Observer API

Cons

  • Requires React as a dependency
  • May not work in older browsers without a polyfill for Intersection Observer
  • Limited to viewport detection (not suitable for other types of element interactions)

Code Examples

Basic usage:

import { useInView } from "react-cool-inview";

function MyComponent() {
  const { ref, inView } = useInView();

  return (
    <div ref={ref}>
      {inView ? "Element is in view" : "Element is out of view"}
    </div>
  );
}

Using callbacks:

const { ref } = useInView({
  onEnter: ({ scrollDirection, entry, observe, unobserve }) => {
    console.log("Element entered view");
  },
  onLeave: ({ scrollDirection, entry, observe, unobserve }) => {
    console.log("Element left view");
  },
});

Custom options:

const { ref, inView } = useInView({
  threshold: 0.5, // Trigger when 50% of the element is in view
  rootMargin: "50px", // Add 50px margin around the viewport
  unobserveOnEnter: true, // Stop observing after entering view once
});

Getting Started

  1. Install the package:

    npm install react-cool-inview
    
  2. Import and use the hook in your component:

    import { useInView } from "react-cool-inview";
    
    function MyComponent() {
      const { ref, inView } = useInView();
    
      return <div ref={ref}>{inView ? "In view" : "Out of view"}</div>;
    }
    
  3. Customize the hook behavior as needed using the available options.

Competitor Comparisons

23,338

Open source, production-ready animation and gesture library for React

Pros of Motion

  • Comprehensive animation library with a wide range of features
  • Declarative API for complex animations and gestures
  • Strong community support and extensive documentation

Cons of Motion

  • Larger bundle size due to its extensive feature set
  • Steeper learning curve for beginners
  • May be overkill for simple intersection detection use cases

Code Comparison

react-cool-inview:

const { ref, inView } = useInView();

return <div ref={ref}>{inView && <p>Element is in view</p>}</div>;

Motion:

<motion.div
  initial={{ opacity: 0 }}
  whileInView={{ opacity: 1 }}
  viewport={{ once: true }}
>
  <p>Element is in view</p>
</motion.div>

Summary

react-cool-inview is a lightweight library focused specifically on intersection detection, making it ideal for simple use cases like lazy loading or infinite scrolling. It has a smaller bundle size and a straightforward API.

Motion, on the other hand, is a full-featured animation library that includes intersection detection among its many capabilities. It offers more advanced animation and gesture controls but comes with a larger bundle size and a steeper learning curve.

Choose react-cool-inview for simple intersection detection needs, and Motion for more complex animation requirements that go beyond just detecting when an element is in view.

React component for the Intersection <Observer /> API

Pros of react-intersection-observer

  • More established and widely used, with a larger community and more frequent updates
  • Provides a simpler API with fewer configuration options, making it easier to use for basic scenarios
  • Offers a higher-order component (withInView) for class components

Cons of react-intersection-observer

  • Less flexible and customizable compared to react-cool-inview
  • Doesn't provide built-in support for tracking multiple elements or custom root margins
  • Lacks some advanced features like trigger-once functionality and custom event callbacks

Code Comparison

react-intersection-observer:

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

const Component = () => {
  const [ref, inView] = useInView();
  return <div ref={ref}>{inView ? 'In view' : 'Not in view'}</div>;
};

react-cool-inview:

import useInView from 'react-cool-inview';

const Component = () => {
  const { ref, inView } = useInView();
  return <div ref={ref}>{inView ? 'In view' : 'Not in view'}</div>;
};

Both libraries provide similar basic functionality, but react-cool-inview offers more advanced features and customization options. The choice between them depends on the specific requirements of your project and the level of control you need over the intersection observer behavior.

Sensor component for React that notifies you when it goes in or out of the window viewport.

Pros of react-visibility-sensor

  • Simpler API with fewer options, making it easier to use for basic visibility detection
  • Lightweight package with minimal dependencies
  • Longer track record and more established in the React ecosystem

Cons of react-visibility-sensor

  • Less flexible configuration options compared to react-cool-inview
  • Doesn't support Intersection Observer API, which can be more performant
  • Lacks advanced features like custom root margin and threshold

Code Comparison

react-visibility-sensor:

<VisibilitySensor onChange={this.onChange}>
  <div>{"I'm the target element"}</div>
</VisibilitySensor>

react-cool-inview:

const { ref, inView } = useInView({
  threshold: 0.25,
  onChange: ({ inView, scrollDirection, entry, observe, unobserve }) => {
    // Triggered when the target meets the threshold
  },
});

return <div ref={ref}>{"I'm the target element"}</div>;

react-cool-inview offers more granular control over the visibility detection process, including support for the Intersection Observer API and additional configuration options. It provides a hook-based approach, which aligns well with modern React practices. On the other hand, react-visibility-sensor has a simpler API and may be sufficient for basic use cases where advanced features are not required.

A React component to execute a function whenever you scroll to an element.

Pros of react-waypoint

  • Lightweight and focused solely on detecting when an element enters or leaves the viewport
  • Well-established project with a longer history and larger community
  • Provides more granular control over when callbacks are triggered (e.g., entering from top vs. bottom)

Cons of react-waypoint

  • Limited to viewport detection, lacking additional features like intersection ratio
  • Doesn't use the Intersection Observer API, potentially less performant for many elements
  • Less frequent updates and maintenance compared to react-cool-inview

Code Comparison

react-waypoint:

<Waypoint
  onEnter={this.handleWaypointEnter}
  onLeave={this.handleWaypointLeave}
>
  <div>Content</div>
</Waypoint>

react-cool-inview:

const { ref, inView } = useInView({
  threshold: 0.25,
  onChange: ({ inView }) => console.log(inView),
});

return <div ref={ref}>{inView && <div>Content</div>}</div>;

react-cool-inview offers a more modern hook-based API and additional features like threshold and rootMargin, while react-waypoint uses a component-based approach with simpler props for basic viewport detection.

React components for efficiently rendering large lists and tabular data

Pros of react-window

  • Highly optimized for rendering large lists and tabular data
  • Supports both fixed-size and variable-size lists
  • Provides advanced features like scrolling to specific items and scroll restoration

Cons of react-window

  • Steeper learning curve due to its more complex API
  • Less flexible for general-purpose intersection observation
  • Focused primarily on virtualized lists, not general viewport detection

Code Comparison

react-window:

import { FixedSizeList } from 'react-window';

const Example = () => (
  <FixedSizeList
    height={400}
    itemCount={1000}
    itemSize={35}
    width={300}
  >
    {({ index, style }) => <div style={style}>Row {index}</div>}
  </FixedSizeList>
);

react-cool-inview:

import useInView from 'react-cool-inview';

const Example = () => {
  const { ref, inView } = useInView();
  return <div ref={ref}>{inView ? 'Visible' : 'Hidden'}</div>;
};

react-window is specialized for efficient rendering of large lists, while react-cool-inview offers a simpler API for general intersection observation. react-window requires more setup but provides powerful virtualization capabilities, whereas react-cool-inview is more straightforward for basic viewport detection tasks.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

REACT COOL INVIEW

A React hook / component API that monitors an element enters or leaves the viewport (or another element) with highly-performant way, using Intersection Observer. It's lightweight and super flexible, which can cover all the cases that you need, like lazy-loading images and videos, infinite scroll web app, triggering animations, tracking impressions, and more. Try it you will 👍🏻 it!

❤️ it? ⭐️ it on GitHub or Tweet about it.

build status coverage status npm version npm downloads npm downloads gzip size All Contributors PRs welcome Twitter URL

demo

⚡️ Try yourself: https://react-cool-inview.netlify.app

Features

Requirement

To use react-cool-inview, you must use react@16.8.0 or greater which includes hooks.

Installation

This package is distributed via npm.

$ yarn add react-cool-inview
# or
$ npm install --save react-cool-inview

Usage

react-cool-inview has a flexible API design, it can cover simple to complex use cases for you. Here are some ideas for how you can use it.

⚠️ Most modern browsers support Intersection Observer natively. You can also add polyfill for full browser support.

Basic usage

To monitor an element enters or leaves the viewport by the inView state and useful sugar events.

import { useInView } from "react-cool-inview";

const App = () => {
  const { observe, unobserve, inView, scrollDirection, entry } = useInView({
    threshold: 0.25, // Default is 0
    onChange: ({ inView, scrollDirection, entry, observe, unobserve }) => {
      // Triggered whenever the target meets a threshold, e.g. [0.25, 0.5, ...]

      unobserve(); // To stop observing the current target element
      observe(); // To re-start observing the current target element
    },
    onEnter: ({ scrollDirection, entry, observe, unobserve }) => {
      // Triggered when the target enters the viewport
    },
    onLeave: ({ scrollDirection, entry, observe, unobserve }) => {
      // Triggered when the target leaves the viewport
    },
    // More useful options...
  });

  return <div ref={observe}>{inView ? "Hello, I am 🤗" : "Bye, I am 😴"}</div>;
};

💡 You don't have to call unobserve when the component is unmounted, this hook will handle it for you.

Using as a Component

Changes HelloText when it enters the viewport. The options can be passed through the props.

import { InView } from "react-cool-inview";

const HelloText = ({ inView, observe }) => (
  <div ref={observe}>{inView ? "Hello, I am 🤗" : "Bye, I am 😴"}</div>
);

const App = () => (
  <InView unobserveOnEnter>
    <HelloText />
  </InView>
);

💡 InView passes observe and other props to the HelloText.

Lazy-loading Images

It's super easy to build an image lazy-loading component with react-cool-inview to boost the performance of your web app.

import { useInView } from "react-cool-inview";

const LazyImage = ({ width, height, ...rest }) => {
  const { observe, inView } = useInView({
    // Stop observe when the target enters the viewport, so the "inView" only triggered once
    unobserveOnEnter: true,
    // For better UX, we can grow the root margin so the image will be loaded before it comes to the viewport
    rootMargin: "50px",
  });

  return (
    <div className="placeholder" style={{ width, height }} ref={observe}>
      {inView && <img {...rest} />}
    </div>
  );
};

💡 Looking for a comprehensive image component? Try react-cool-img, it's my other component library.

Infinite Scroll

Infinite scroll is a popular design technique like Facebook and Twitter feed etc., new content being loaded as you scroll down a page. The basic concept as below.

import { useState } from "react";
import { useInView } from "react-cool-inview";
import axios from "axios";

const App = () => {
  const [todos, setTodos] = useState(["todo-1", "todo-2", "..."]);
  const { observe } = useInView({
    // For better UX, we can grow the root margin so the data will be loaded earlier
    rootMargin: "50px 0px",
    // When the last item comes to the viewport
    onEnter: ({ unobserve }) => {
      // Pause observe when loading data
      unobserve();
      // Load more data
      axios.get("/todos").then((res) => {
        setTodos([...todos, ...res.todos]);
      });
    },
  });

  return (
    <div>
      {todos.map((todo, idx) => (
        <div ref={idx === todos.length - 1 ? observe : null}>{todo}</div>
      ))}
    </div>
  );
};

💡 Compare to pagination, infinite scroll provides a seamless experience for users and it’s easy to see the appeal. But when it comes to render a large lists, performance will be a problem. But don't worry, react-cool-virtual can help you out!

Trigger Animations

Another great use case is to trigger CSS animations once they are visible to the users.

import { useInView } from "react-cool-inview";

const App = () => {
  const { observe, inView } = useInView({
    // Stop observe when the target enters the viewport, so the "inView" only triggered once
    unobserveOnEnter: true,
    // Shrink the root margin, so the animation will be triggered once the target reach a fixed amount of visible
    rootMargin: "-100px 0px",
  });

  return (
    <div className="container" ref={observe}>
      <div className={inView ? "fade-in" : ""}>I'm a 🍟</div>
    </div>
  );
};

Track Impressions

react-cool-inview can also play as an impression tracker, helps you fire an analytic event when a user sees an element or advertisement.

import { useInView } from "react-cool-inview";

const App = () => {
  const { observe } = useInView({
    // For an element to be considered "seen", we'll say it must be 100% in the viewport
    threshold: 1,
    onEnter: ({ unobserve }) => {
      // Stop observe when the target enters the viewport, so the callback only triggered once
      unobserve();
      // Fire an analytic event to your tracking service
      someTrackingService.send("🍋 is seen");
    },
  });

  return <div ref={observe}>I'm a 🍋</div>;
};

Scrolling Direction

react-cool-inview not only monitors an element enters or leaves the viewport but also tells you its scroll direction by the scrollDirection object. The object contains vertical (y-axios) and horizontal (x-axios) properties, they're calculated whenever the target element meets a threshold. If there's no enough condition for calculating, the value of the properties will be undefined. In addition, the value of the properties will sync with the scrolling direction of the viewport.

import { useInView } from "react-cool-inview";

const App = () => {
  const {
    observe,
    inView,
    // vertical will be "up" or "down", horizontal will be "left" or "right"
    scrollDirection: { vertical, horizontal },
  } = useInView({
    // Scroll direction is calculated whenever the target meets a threshold
    // more trigger points the calculation will be more instant and accurate
    threshold: [0.2, 0.4, 0.6, 0.8, 1],
    onChange: ({ scrollDirection }) => {
      // We can also access the scroll direction from the event object
      console.log("Scroll direction: ", scrollDirection.vertical);
    },
  });

  return (
    <div ref={observe}>
      <div>{inView ? "Hello, I am 🤗" : "Bye, I am 😴"}</div>
      <div>{`You're scrolling ${vertical === "up" ? "⬆️" : "⬇️"}`}</div>
    </div>
  );
};

If you jump to a section by the Element.scrollTop and encounter the wrong value of the scrollDirection. You can use updatePosition method to correct the behavior.

import { useEffect } from "react";
import { useInView } from "react-cool-inview";

const App = () => {
  const { observe, scrollDirection, updatePosition } = useInView({
    threshold: [0.2, 0.4, 0.6, 0.8, 1],
  });

  useEffect(() => {
    window.scrollTo(0, 500);
    updatePosition(); // Ensure the target element's position has been updated after the "window.scrollTo"
  }, []);

  return (
    <div ref={observe}>
      <div>{`You're scrolling ${
        scrollDirection.vertical === "up" ? "⬆️" : "⬇️"
      }`}</div>
    </div>
  );
};

Intersection Observer v2

The Intersection Observer v1 can perfectly tell you when an element is scrolled into the viewport, but it doesn't tell you whether the element is covered by something else on the page or whether the element has any visual effects applied to it (like transform, opacity, filter etc.) that can make it invisible. The main concern that has surfaced is how this kind of knowledge could be helpful in preventing clickjacking and UI redress attacks (read this article to learn more).

If you want to track the click-through rate (CTR) or impression of an element, which is actually visible to a user, Intersection Observer v2 can be the savior. Which introduces a new boolean field named isVisible. A true value guarantees that an element is visible on the page and has no visual effects applied on it. A false value is just the opposite. The characteristic of the isVisible is integrated with the inView state and related events (like onEnter, onLeave etc.) to provide a better DX for you.

When using the v2, there're somethings we need to know:

To use Intersection Observer v2, we must set the trackVisibility and delay options.

import { useInView } from "react-cool-inview";

const App = () => {
  // With Intersection Observer v2, the "inView" not only tells you the target
  // is intersecting with the root, but also guarantees it's visible on the page
  const { observe, inView } = useInView({
    // Track the actual visibility of the target
    trackVisibility: true,
    // Set a minimum delay between notifications, it must be set to 100 (ms) or greater
    // For performance perspective, use the largest tolerable value as much as possible
    delay: 100,
    onEnter: () => {
      // Triggered when the target is visible and enters the viewport
    },
    onLeave: () => {
      // Triggered when the target is visible and leaves the viewport
    },
  });

  return <div ref={observe}>{inView ? "Hello, I am 🤗" : "Bye, I am 😴"}</div>;
};

How to Share A ref?

You can share a ref as follows:

import { useRef } from "react";
import { useInView } from "react-cool-inview";

const App = () => {
  const ref = useRef();
  const { observe } = useInView();

  return (
    <div
      ref={(el) => {
        observe(el); // Set the target element for monitoring
        ref.current = el; // Share the element for other purposes
      }}
    />
  );
};

Working in TypeScript

This hook supports TypeScript, you can tell the hook what type of element you are going to observe through the generic type:

const App = () => {
  const { observe } = useInView<HTMLDivElement>();

  return <div ref={observe} />;
};

💡 For more available types, please check it out.

API

const returnObj = useInView(options?: object);

Return object

It's returned with the following properties.

KeyTypeDefaultDescription
observefunctionTo set a target element for monitoring or re-start observing the current target element.
unobservefunctionTo stop observing the current target element.
inViewbooleanThe visible state of the target element. If it's true, the target element has become at least as visible as the threshold that was passed. If it's false, the target element is no longer as visible as the given threshold. Supports Intersection Observer v2.
scrollDirectionobjectThe scroll direction of the target element. Which contains vertical and horizontal properties. See scroll direction for more information.
entryobjectThe IntersectionObserverEntry of the target element. Which may contain the isVisible property of the Intersection Observer v2, depends on the browser compatibility.
updatePositionfunctionTo update the current position of the target element for some cases.

Parameter

The options provides the following configurations and event callbacks for you.

KeyTypeDefaultDescription
rootHTMLElementwindowThe element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null.
rootMarginstring0pxMargin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections.
thresholdnumber | number[]0Indicates 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 to run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1].
trackVisibilitybooleanfalseIndicates whether the intersection observer will track changes in a target’s visibility. It's required when using Intersection Observer v2.
delaynumberIndicates the minimum delay in milliseconds between notifications from the intersection observer for a given target. It's required when using Intersection Observer v2.
unobserveOnEnterbooleanfalseStops observe once the target element intersects with the intersection observer's root. It's useful when you only want to trigger the hook once, e.g. scrolling to run animations.
onChangefunctionIt's invoked whenever the target element meets a threshold specified for the intersection observer. The callback receives an event object which the same with the return object of the hook.
onEnterfunctionIt's invoked when the target element enters the viewport. The callback receives an event object which the same with the return object of the hook except for inView. Supports Intersection Observer v2.
onLeavefunctionIt's invoked when the target element leaves the viewport. The callback receives an event object which the same with the return object of the hook except for inView. Supports Intersection Observer v2.

rootMargin Not Working As Expected?

If your web app is running in an <iframe> or you have a custom root, the viewport won't be the current document. Read the doc to understand how do root and root margin work.

Intersection Observer Polyfill

Intersection Observer has good support amongst browsers, but it's not universal. You'll need to polyfill browsers that don't support it. Polyfills is something you should do consciously at the application level. Therefore react-cool-inview doesn't include it.

You can use W3C's polyfill:

$ yarn add intersection-observer
# or
$ npm install --save intersection-observer

Then import it at your app's entry point:

import "intersection-observer";

Or use dynamic imports to only load the file when the polyfill is required:

(async () => {
  if (!("IntersectionObserver" in window))
    await import("intersection-observer");
})();

Polyfill.io is an alternative way to add the polyfill when needed.

Performance Issues

Be aware that the callback of the onChange event is executed on the main thread, it should operate as quickly as possible. If any time-consuming needs to be done, use requestIdleCallback or setTimeout.

onChange = (event) => requestIdleCallback(() => this.handleChange(event));

Articles / Blog Posts

💡 If you have written any blog post or article about react-cool-inview, please open a PR to add it here.

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Welly
Welly

💻 📖 🚧
Nhan Nguyen
Nhan Nguyen

💻
Yann Pringault
Yann Pringault

🐛
gabalafou
gabalafou

📖
Victor
Victor

🐛 🚇
Max
Max

💻
SaidMarar
SaidMarar

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

NPM DownloadsLast 30 Days