✨ Introducing react-cool-inview: React hook to monitor an element enters or leaves the viewport (or another element)
Tags: react, hook, opensource, javascript
Published: 2020-05-01 14:47:11

[react-cool-inview](https://github.com/wellyshen/react-cool-inview) is a React [hook](https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook) / component API that monitors an element enters or leaves the viewport (or another element) with performant and efficient way, using [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). It's lightweight and super flexible, which can cover all the cases that you need, like [lazy-loading images](#lazy-loading-images) and videos, [infinite scroll](#infinite-scroll) web app, [triggering animations](#trigger-animations), [tracking impressions](#track-impressions), and more. Try it you will ❤️ it!

![demo](https://user-images.githubusercontent.com/21308003/91034314-6af12600-e637-11ea-8a78-76c77ecaea51.gif)

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

## Features

- 🚀 Monitors elements with performant and non-main-thread blocking way, using [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
- 🎣 Easy to use, based on React [hook](https://reactjs.org/docs/hooks-custom.html#using-a-custom-hook) / [component](#using-as-a-component).
- 🎛 Super flexible [API](https://github.com/wellyshen/react-cool-inview#api) design which can cover [all the cases](#usage) that you need.
- 🖱️ Supports [scroll direction](#scrolling-direction), cool right?
- ✌🏻 Supports [Intersection Observer v2](#intersection-observer-v2).
(https://github.com/wellyshen/react-cool-inview#use-your-own-ref).
- 📜 Supports [TypeScript](https://github.com/wellyshen/react-cool-inview#working-in-typescript) type definition.
- 🗄️ Server-side rendering compatibility.
- 🦔 Tiny size ([~ 1.2kB gzipped](https://bundlephobia.com/result?p=react-cool-inview)). No external dependencies, aside for the `react`.

## Usage

`react-cool-inview` has a flexible [API](https://github.com/wellyshen/react-cool-inview#api) design, it can cover simple to complex use cases for you. Here are some ideas for how you can use it.

### Basic Use Case

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

```js
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>;
};
```

### Using as a Component

Changes `HelloText` when it enters the viewport. The [options](https://github.com/wellyshen/react-cool-inview#parameter) can be passed through the props.

```js
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](https://github.com/wellyshen/react-cool-inview#return-object) 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.

```js
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>
  );
};
```

### 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.

```js
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, observe }) => {
      // Pause observe when loading data
      unobserve();
      // Load more data
      axios.get("/todos").then((res) => {
        setTodos([...todos, ...res.todos]);
        // Resume observe after loading data
        observe();
      });
    },
  });

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

### Trigger Animations

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

```js
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.

```js
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.

```js
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>
  );
};
```

## 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](https://en.wikipedia.org/wiki/Clickjacking) and UI redress attacks (read this [article](https://developers.google.com/web/updates/2019/02/intersectionobserver-v2) 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](https://developers.google.com/web/updates/2019/02/intersectionobserver-v2) can be the savior. Which introduces a new boolean field named [isVisible](https://w3c.github.io/IntersectionObserver/v2/#dom-intersectionobserverentry-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:

- Check [browser compatibility](https://caniuse.com/#feat=intersectionobserver-v2). If a browser doesn't support the v2, we will fallback to the v1 behavior.
- Understand [how visibility is calculated](https://w3c.github.io/IntersectionObserver/v2/#calculate-visibility-algo).
- Visibility is much more expensive to compute than intersection, only use it when needed.

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

```js
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>;
};
```

---

Thanks for reading, for more usage details check out the project's GitHub page: https://github.com/wellyshen/react-cool-inview

You can also install this package is distributed via [npm](https://www.npmjs.com/package/react-cool-inview).

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