All Projects → wellyshen → React Cool Inview

wellyshen / React Cool Inview

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

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to React Cool Inview

React Intersection Observer
React implementation of the Intersection Observer API to tell you when an element enters or leaves the viewport.
Stars: ✭ 2,689 (+223.98%)
Mutual labels:  hook, monitor, performance, lazy-loading, viewport, scrolling
Viewprt
A tiny, dependency-free, high performance viewport position & intersection observation tool
Stars: ✭ 36 (-95.66%)
Mutual labels:  lazy-loading, viewport, scrolling
react-cool-virtual
😎 ♻️ A tiny React hook for rendering large datasets like a breeze.
Stars: ✭ 1,031 (+24.22%)
Mutual labels:  hook, lazy-loading
use-scroll-direction
A simple, performant, and cross-browser hook for detecting scroll direction in your next react app.
Stars: ✭ 24 (-97.11%)
Mutual labels:  hook, scrolling
Use Web Animations
😎 🍿 React hook for highly-performant and manipulable animations using Web Animations API.
Stars: ✭ 802 (-3.37%)
Mutual labels:  hook, performance
react-scroll-trigger
📜 React component that monitors scroll events to trigger callbacks when it enters, exits and progresses through the viewport. All callback include the progress and velocity of the scrolling, in the event you want to manipulate stuff based on those values.
Stars: ✭ 126 (-84.82%)
Mutual labels:  scrolling, viewport
onscroll-effect
A tiny JavaScript library to enable CSS animations when user scrolls.
Stars: ✭ 35 (-95.78%)
Mutual labels:  scrolling, viewport
svelte-intersection-observer
Detect if an element is in the viewport using the Intersection Observer API
Stars: ✭ 151 (-81.81%)
Mutual labels:  viewport, lazy-loading
App perf
Open source application performance monitoring tool with emphasis on ease of setup and use. Providing similar functionality like NewRelic/AppNeta/Skylight etc.
Stars: ✭ 353 (-57.47%)
Mutual labels:  monitor, performance
React On Screen
Check if a react component in the viewport
Stars: ✭ 357 (-56.99%)
Mutual labels:  lazy-loading, viewport
React Cool Dimensions
😎 📏 React hook to measure an element's size and handle responsive components.
Stars: ✭ 419 (-49.52%)
Mutual labels:  hook, performance
render-props
㸚 Easy-to-use React state containers which utilize the render props (function as child) pattern
Stars: ✭ 33 (-96.02%)
Mutual labels:  scrolling, viewport
angular-inviewport
A simple lightweight library for Angular with no other dependencies that detects when an element is within the browser viewport and adds a "sn-viewport-in" or "sn-viewport-out" class to the element
Stars: ✭ 72 (-91.33%)
Mutual labels:  scrolling, viewport
vue-in-viewport-mixin
Vue 2 mixin to determine when a DOM element is visible in the client window
Stars: ✭ 99 (-88.07%)
Mutual labels:  scrolling, viewport
Ios Monitor Platform
📚 iOS 性能监控 SDK —— Wedjat(华狄特)开发过程的调研和整理
Stars: ✭ 2,316 (+179.04%)
Mutual labels:  hook, monitor
react-awesome-reveal
React components to add reveal animations using the Intersection Observer API and CSS Animations.
Stars: ✭ 564 (-32.05%)
Mutual labels:  animations, viewport
Viewport Checker
Little utility to detect if elements are currently within the viewport 🔧
Stars: ✭ 596 (-28.19%)
Mutual labels:  animations, viewport
Easy Monitor
企业级 Node.js 应用性能监控与线上故障定位解决方案
Stars: ✭ 2,451 (+195.3%)
Mutual labels:  monitor, performance
Droidtelescope
DroidTelescope(DT),Android端App性能监控框架
Stars: ✭ 231 (-72.17%)
Mutual labels:  monitor, performance
React Awesome Reveal
React components to add reveal animations using the Intersection Observer API and CSS Animations.
Stars: ✭ 346 (-58.31%)
Mutual labels:  animations, viewport

REACT COOL INVIEW

A React hook 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 scrolling 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 npm bundle size MIT licensed 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 [email protected] 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 Use Case

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 { ref, inView, scrollDirection, entry, observe, unobserve } = 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, ...]
      },
      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={ref}>{inView ? "Hello, I am 🤗" : "Bye, I am 😴"}</div>;
};

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 { ref, 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={ref}>
      {inView && <img {...rest} />}
    </div>
  );
};

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

Infinite Scrolling

Infinite scrolling 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 { ref } = useInView({
    // For better UX, we can grow the root margin so the data will be loaded before a user sees the loading indicator
    rootMargin: "50px 0",
    // When the loading indicator 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) => (
        <div>{todo}</div>
      ))}
      <div ref={ref}>Loading...</div>
    </div>
  );
};

Compare to pagination, infinite scrolling 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. We can use react-window to address the problem by the technique of DOM recycling.

import { useState } from "react";
import useInView from "react-cool-inview";
import { FixedSizeList as List } from "react-window";
import axios from "axios";

const Row = ({ index, data, style }) => {
  const { todos, handleLoadingInView } = data;
  const isLast = index === todos.length;
  const { ref } = useInView({ onEnter: handleLoadingInView });

  return (
    <div style={style} ref={isLast ? ref : null}>
      {isLast ? "Loading..." : todos[index]}
    </div>
  );
};

const App = () => {
  const [todos, setTodos] = useState(["todo-1", "todo-2", "..."]);
  const [isFetching, setIsFetching] = useState(false);

  const handleLoadingInView = () => {
    // Row component is dynamically created by react-window, we need to use the "isFetching" flag
    // instead of unobserve/observe to avoid re-fetching data
    if (!isFetching)
      axios.get("/todos").then((res) => {
        setTodos([...todos, ...res.todos]);
        setIsFetching(false);
      });

    setIsFetching(true);
  };

  // Leverage the power of react-window to help us address the performance bottleneck
  return (
    <List
      height={150}
      itemCount={todos.length + 1} // Last one is for the loading indicator
      itemSize={35}
      width={300}
      itemData={{ todos, handleLoadingInView }}
    >
      {Row}
    </List>
  );
};

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 { ref, 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 0",
  });

  return (
    <div className="container" ref={ref}>
      <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 { ref } = 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={ref}>I'm a 🍋</div>;
};

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

import useInView from "react-cool-inview";

const App = () => {
  const {
    ref,
    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={ref}>
      <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 { ref, scrollDirection, updatePosition } = useInView({
    threshold: [0.2, 0.4, 0.6, 0.8, 1],
  });

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

  return (
    <div ref={ref}>
      <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 on 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 something 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 { ref, 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={ref}>{inView ? "Hello, I am 🤗" : "Bye, I am 😴"}</div>;
};

Use Your Own ref

In case of you had a ref already or you want to share a ref for other purposes. You can pass in the ref instead of using the one provided by this hook.

const ref = useRef();
const { inView } = useInView({ ref });

Working in TypeScript

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

import useInView from "react-cool-inview";

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

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

API

const returnObj = useInView(options?: object);

Return object

It's returned with the following properties.

Key Type Default Description
ref object Used to set the target element for monitoring.
inView boolean The 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.
scrollDirection object The scroll direction of the target element. Which contains vertical and horizontal properties. See scroll direction for more information.
entry object The IntersectionObserverEntry of the target element. Which may contain the isVisible property of the Intersection Observer v2, depends on the browser compatibility.
unobserve function To stop observing the target element.
observe function To re-start observing the target element once it's stopped observing.
updatePosition function To update the current position of the target element for some cases.

Parameter

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

Key Type Default Description
ref object For some reasons, you can pass in your own ref instead of using the built-in.
root HTMLElement window The 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.
rootMargin string 0px Margin 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.
threshold number | number[] 0 Indicates 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].
trackVisibility boolean false Indicates whether the intersection observer will track changes in a target’s visibility. It's required when using Intersection Observer v2.
delay number Indicates the minimum delay in milliseconds between notifications from the intersection observer for a given target. It's required when using Intersection Observer v2.
unobserveOnEnter boolean false Stops 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.
onChange function It'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.
onEnter function It'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.
onLeave function It'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.

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));

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Welly

💻 📖 🚧

Nhan Nguyen

💻

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

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].