Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

React implementation of the Intersection Observer API to tell you when an element enters or leaves the viewport.

License

NotificationsYou must be signed in to change notification settings

thebuilder/react-intersection-observer

Repository files navigation

Version BadgeTestLicenseDownloads

A React implementation of theIntersection Observer APIto tell you when an element enters or leaves the viewport. ContainsHooks,render props, andplain children implementations.

Features

  • 🪝Hooks or Component API - WithuseInView anduseOnInView it's easierthan ever to monitor elements
  • ⚡️Optimized performance - Reuses Intersection Observer instances wherepossible
  • ⚙️Matches native API - Intuitive to use
  • 🛠Written in TypeScript - It'll fit right into your existing TypeScriptproject
  • 🧪Ready to test - Mocks the Intersection Observer for easy testing withJest orVitest
  • 🌳Tree-shakeable - Only include the parts you use
  • 💥Tiny bundle - Around~1.15kB foruseInView and~1.6kB for<InView>

Open in StackBlitz

Installation

Install the package with your package manager of choice:

npm install react-intersection-observer --save

Usage

useInView hook

// Use object destructuring, so you don't need to remember the exact orderconst{ ref, inView, entry}=useInView(options);// Or array destructuring, making it easy to customize the field namesconst[ref,inView,entry]=useInView(options);

TheuseInView hook makes it easy to monitor theinView state of yourcomponents. Call theuseInView hook with the (optional)optionsyou need. It will return an array containing aref, theinView status andthe currententry.Assign theref to the DOM element you want to monitor, and the hook willreport the status.

importReactfrom"react";import{useInView}from"react-intersection-observer";constComponent=()=>{const{ ref, inView, entry}=useInView({/* Optional options */threshold:0,});return(<divref={ref}><h2>{`Header inside viewport${inView}.`}</h2></div>);};

Note: The firstfalse notification from the underlying IntersectionObserver is ignored so your handlers only run after a real visibility change. Subsequent transitions still report bothtrue andfalse states as the element enters and leaves the viewport.

useOnInView hook

constinViewRef=useOnInView((inView,entry)=>{if(inView){// Do something with the element that came into viewconsole.log("Element is in view",entry.target);}else{console.log("Element left view",entry.target);}},options// Optional IntersectionObserver options);

TheuseOnInView hook provides a more direct alternative touseInView. Ittakes a callback function and returns a ref that you can assign to the DOMelement you want to monitor. Whenever the element enters or leaves the viewport,your callback will be triggered with the latest in-view state.

Key differences fromuseInView:

  • No re-renders - This hook doesn't update any state, making it ideal forperformance-critical scenarios
  • Direct element access - Your callback receives the actualIntersectionObserverEntry with thetarget element
  • Boolean-first callback - The callback receives the currentinViewboolean as the first argument, matching theonChange signature fromuseInView
  • Similar options - Accepts all the sameoptions asuseInViewexceptonChange,initialInView, andfallbackInView

Note: Just likeuseInView, the initialfalse notification is skipped. Your callback fires the first time the element becomes visible (and on every subsequent enter/leave transition).

importReactfrom"react";import{useOnInView}from"react-intersection-observer";constComponent=()=>{// Track when element appears without causing re-rendersconsttrackingRef=useOnInView((inView,entry)=>{if(inView){// Element is in view - perhaps log an impressionconsole.log("Element appeared in view",entry.target);}else{console.log("Element left view",entry.target);}},{/* Optional options */threshold:0.5,triggerOnce:true,},);return(<divref={trackingRef}><h2>This element is being tracked without re-renders</h2></div>);};

Render props

To use the<InView> component, you pass it a function. It will be calledwhenever the state changes, with the new value ofinView. In addition to theinView prop, children also receive aref that should be set on thecontaining DOM element. This is the element that the Intersection Observer willmonitor.

If you need it, you can also access theIntersectionObserverEntryonentry, giving you access to all the details about the current intersectionstate.

import{InView}from"react-intersection-observer";constComponent=()=>(<InView>{({ inView, ref, entry})=>(<divref={ref}><h2>{`Header inside viewport${inView}.`}</h2></div>)}</InView>);exportdefaultComponent;

Note:<InView> mirrors the hook behaviour—it suppresses the very firstfalse notification so render props andonChange handlers only run after a genuine visibility change.

Plain children

You can pass any element to the<InView />, and it will handle creating thewrapping DOM element. Add a handler to theonChange method, and control thestate in your own component. Any extra props you add to<InView> will bepassed to the HTML element, allowing you set theclassName,style, etc.

import{InView}from"react-intersection-observer";constComponent=()=>(<InViewas="div"onChange={(inView,entry)=>console.log("Inview:",inView)}><h2>Plain children are always rendered. Use onChange to monitor state.</h2></InView>);exportdefaultComponent;

Note

When rendering a plain child, make sure you keep your HTML outputsemantic. Change theas to match the context, and add aclassName to stylethe<InView />. The component does not support Ref Forwarding, so if youneed aref to the HTML element, use the Render Props version instead.

API

Options

Provide these as the options argument in theuseInView hook or as props on the<InView /> component.

NameTypeDefaultDescription
rootElementdocumentThe Intersection Observer interface's read-only root property identifies the Element or Document whose bounds are treated as the bounding box of the viewport for the element which is the observer's target. If the root isnull, then the bounds of the actual document viewport are used.
rootMarginstring'0px'Margin around the root. Can have values similar to the CSS margin property, e.g."10px 20px 30px 40px" (top, right, bottom, left). Also supports percentages, to check if an element intersects with the center of the viewport for example"-50% 0% -50% 0%".
thresholdnumber ornumber[]0Number between0 and1 indicating the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points.
onChange(inView, entry) => voidundefinedCall this function whenever the in view state changes. It will receive theinView boolean, alongside the currentIntersectionObserverEntry.
trackVisibility 🧪booleanfalseA boolean indicating whether this Intersection Observer will track visibility changes on the target.
delay 🧪numberundefinedA number indicating the minimum delay in milliseconds between notifications from this observer for a given target. This must be set to at least100 iftrackVisibility istrue.
skipbooleanfalseSkip creating the IntersectionObserver. You can use this to enable and disable the observer as needed. Ifskip is set whileinView, the current state will still be kept.
triggerOncebooleanfalseOnly trigger the observer once.
initialInViewbooleanfalseSet the initial value of theinView boolean. This can be used if you expect the element to be in the viewport to start with, and you want to trigger something when it leaves.
fallbackInViewbooleanundefinedIf theIntersectionObserver API isn't available in the client, the default behavior is to throw an Error. You can set a specific fallback behavior, and theinView value will be set to this instead of failing. To set a global default, you can set it with thedefaultFallbackInView()

useOnInView accepts the same options asuseInView exceptonChange,initialInView, andfallbackInView.

InView Props

The<InView /> component also accepts the following props:

NameTypeDefaultDescription
asIntrinsicElement'div'Render the wrapping element as this element. Defaults todiv. If you want to use a custom component, please use theuseInView hook or a render prop instead to manage the reference explictly.
children({ref, inView, entry}) => ReactNode orReactNodeundefinedChildren expects a function that receives an object containing theinView boolean and aref that should be assigned to the element root. Alternatively pass a plain child, to have the<InView /> deal with the wrapping element. You will also get theIntersectionObserverEntry asentry, giving you more details.

Intersection Observer v2 🧪

The newv2 implementation of IntersectionObserverextends the original API, so you can track if the element is covered by anotherelement or has filters applied to it. Useful for blocking clickjacking attemptsor tracking ad exposure.

To use it, you'll need to add the newtrackVisibility anddelay options.When you get theentry back, you can then monitor ifisVisible istrue.

constTrackVisible=()=>{const{ ref, entry}=useInView({trackVisibility:true,delay:100});return<divref={ref}>{entry?.isVisible}</div>;};

This is still a very new addition, so checkcaniuse for current browsersupport. IftrackVisibility has been set, and the current browser doesn'tsupport it, a fallback has been added to always reportisVisible astrue.

It's not added to the TypeScriptlib.d.ts file yet, so you will also have toextend theIntersectionObserverEntry with theisVisible boolean.

Recipes

TheIntersectionObserver itself is just a simple but powerful tool. Here's afew ideas for how you can use it.

FAQ

How can I assign multiple refs to a component?

You can wrap multipleref assignments in a singleuseCallback:

importReact,{useRef,useCallback}from"react";import{useInView}from"react-intersection-observer";functionComponent(props){constref=useRef();const{ref:inViewRef, inView}=useInView();// Use `useCallback` so we don't recreate the function on each renderconstsetRefs=useCallback((node)=>{// Ref's from useRef needs to have the node assigned to `current`ref.current=node;// Callback refs, like the one from `useInView`, is a function that takes the node as an argumentinViewRef(node);},[inViewRef],);return<divref={setRefs}>Shared ref is visible:{inView}</div>;}

rootMargin isn't working as expected

When usingrootMargin, the margin gets added to the currentroot - If yourapplication is running inside a<iframe>, or you have defined a customrootthis will not be the current viewport.

You can read more about this on these links:

Testing

Tip

Consider usingVitest Browser Mode instead ofjsdom orhappy-dom.This option allows you to utilize the real browser implementation and triggers correctly when scrolling or adding elements to the viewport. You can skip thereact-intersection-observer/test-utils, or use it as needed.

In order to write meaningful tests, theIntersectionObserver needs to bemocked. You can use the includedreact-intersection-observer/test-utils tohelp with this. It mocks theIntersectionObserver, and includes a few methodsto assist with faking theinView state. When setting theisIntersectingvalue you can pass either aboolean value or a threshold between 0 and 1. Itwill emulate the real IntersectionObserver, allowing you to validate that yourcomponents are behaving as expected.

MethodDescription
mockAllIsIntersecting(isIntersecting)SetisIntersecting on all current Intersection Observer instances. The value ofisIntersecting should be either aboolean or a threshold between 0 and 1.
mockIsIntersecting(element, isIntersecting)SetisIntersecting for the Intersection Observer of a specificelement. The value ofisIntersecting should be either aboolean or a threshold between 0 and 1.
intersectionMockInstance(element)Call theintersectionMockInstance method with an element, to get the (mocked)IntersectionObserver instance. You can use this to spy on theobserve andunobserve methods.
setupIntersectionMocking(mockFn)Mock theIntersectionObserver, so we can interact with them in tests - Should be called inbeforeEach. (Done automatically in Jest environment)
resetIntersectionMocking()Reset the mocks onIntersectionObserver - Should be called inafterEach. (Done automatically in Jest/Vitest environment)
destroyIntersectionMocking()Destroy the mockedIntersectionObserver function, and returnwindow.IntersectionObserver to the original browser implementation

Testing Libraries

This library comes with built-in support for writing tests in bothJest andVitest

Jest

Testing with Jest should work out of the box. Just import thereact-intersection-observer/test-utils in your test files, and you can use themocking methods.

Vitest

If you're running Vitest withglobals,then it'll automatically mock the IntersectionObserver, just like running withJest. Otherwise, you'll need to manually setup/reset the mocking in either theindividual tests, or asetup file.

import{vi,beforeEach,afterEach}from"vitest";import{setupIntersectionMocking,resetIntersectionMocking,}from"react-intersection-observer/test-utils";beforeEach(()=>{setupIntersectionMocking(vi.fn);});afterEach(()=>{resetIntersectionMocking();});

You only need to do this if the test environment does not supportbeforeEachglobally, alongside eitherjest.fn orvi.fn.

Other Testing Libraries

See the instructions forVitest. You should be able to use a similarsetup/reset code, adapted to the testing library you are using. Failing that,copy the code fromtest-utils.ts, and make your own version.

Fallback Behavior

You can create aJest setup filethat leverages theunsupported fallbackoption. In this case, you can override theIntersectionObserver in test fileswere you actively importreact-intersection-observer/test-utils.

test-setup.js

import{defaultFallbackInView}from"react-intersection-observer";defaultFallbackInView(true);// or `false` - whichever consistent behavior makes the most sense for your use case.

Alternatively, you can mock the Intersection Observer in all tests with a globalsetup file. Addreact-intersection-observer/test-utils tosetupFilesAfterEnvin the Jest config, orsetupFiles inVitest.

module.exports={setupFilesAfterEnv:["react-intersection-observer/test-utils"],};

Test Example

importReactfrom"react";import{screen,render}from"@testing-library/react";import{useInView}from"react-intersection-observer";import{mockAllIsIntersecting,mockIsIntersecting,intersectionMockInstance,}from"react-intersection-observer/test-utils";constHookComponent=({ options})=>{const{ ref, inView}=useInView(options);return(<divref={ref}data-testid="wrapper">{inView.toString()}</div>);};test("should create a hook inView",()=>{render(<HookComponent/>);// This causes all (existing) IntersectionObservers to be set as intersectingmockAllIsIntersecting(true);screen.getByText("true");});test("should create a hook inView with threshold",()=>{render(<HookComponentoptions={{threshold:0.3}}/>);mockAllIsIntersecting(0.1);screen.getByText("false");// Once the threshold has been passed, it will trigger inView.mockAllIsIntersecting(0.3);screen.getByText("true");});test("should mock intersecing on specific hook",()=>{render(<HookComponent/>);constwrapper=screen.getByTestId("wrapper");// Set the intersection state on the wrapper.mockIsIntersecting(wrapper,0.5);screen.getByText("true");});test("should create a hook and call observe",()=>{const{ getByTestId}=render(<HookComponent/>);constwrapper=getByTestId("wrapper");// Access the `IntersectionObserver` instance for the wrapper Element.constinstance=intersectionMockInstance(wrapper);expect(instance.observe).toHaveBeenCalledWith(wrapper);});

Intersection Observer

Intersection Observeris the API used to determine if an element is inside the viewport or not.Browser support is excellent -WithSafari adding support in 12.1,all major browsers now support Intersection Observers natively. Add thepolyfill, so it doesn't break on older versions of iOS and IE11.

Unsupported fallback

If the client doesn't have support for theIntersectionObserver, then thedefault behavior is to throw an error. This will crash the React application,unless you capture it with an Error Boundary.

If you prefer, you can set a fallbackinView value to use if theIntersectionObserver doesn't exist. This will makereact-intersection-observer fail gracefully, but you must ensure yourapplication can correctly handle all your observers firing eithertrue orfalse at the same time.

You can set the fallback globally:

import{defaultFallbackInView}from"react-intersection-observer";defaultFallbackInView(true);// or 'false'

You can also define the fallback locally onuseInView or<InView> as anoption. This will override the global fallback value.

importReactfrom"react";import{useInView}from"react-intersection-observer";constComponent=()=>{const{ ref, inView, entry}=useInView({fallbackInView:true,});return(<divref={ref}><h2>{`Header inside viewport${inView}.`}</h2></div>);};

Polyfill

You can import thepolyfill directly or usea service likehttps://cdnjs.cloudflare.com/polyfill to add it whenneeded.

yarn add intersection-observer

Then import it in your app:

import"intersection-observer";

If you are using Webpack (or similar) you could usedynamic imports,to load the Polyfill only if needed. A basic implementation could look somethinglike this:

/** * Do feature detection, to figure out which polyfills needs to be imported. **/asyncfunctionloadPolyfills(){if(typeofwindow.IntersectionObserver==="undefined"){awaitimport("intersection-observer");}}

Low level API

You can access theobserve method, thatreact-intersection-observer uses internally to create and destroyIntersectionObserver instances. This allows you to handle more advanced usecases, where you need full control over when and how observers are created.

import{observe}from"react-intersection-observer";constdestroy=observe(element,callback,options);
NameTypeRequiredDescription
elementElementtrueDOM element to observe
callbackObserverInstanceCallbacktrueThe callback function that Intersection Observer will call
optionsIntersectionObserverInitfalseThe options for the Intersection Observer

Theobserve method returns anunobserve function, that you must call inorder to destroy the observer again.

Important

You most likely won't need this, but it can be useful if youneed to handle IntersectionObservers outside React, or need full control overhow instances are created.


[8]ページ先頭

©2009-2025 Movatter.jp