React component that reports the current scroll percentage of a element inside the viewport. Contains both a Hooks, render props and plain children implementation.
- 🎣 Hooks or Component API - With
useScrollPercentage
it's easier than ever to monitor elements - ⚡️ Optimized performance - Uses React Intersection Observer to only update when elements are inside the viewport
- 🌳 Tree-shakeable - Only include the parts you use
Install using Yarn:
yarn add react-scroll-percentage
or NPM:
npm install react-scroll-percentage --save
⚠️ You also want to add the intersection-observer polyfill for full browser support. Check out adding the polyfill for details about how you can include it.
const [ref, percentage] = useScrollPercentage(options)
Call the useScrollPercentage
hook, with the (optional) options you
need. It will return an array containing a ref
, the current scroll
percentage
and the current
IntersectionObserverEntry
.
Assign the ref
to the DOM element you want to monitor, and the hook will
report the status.
import React from 'react'
import { useScrollPercentage } from 'react-scroll-percentage'
const Component = () => {
const [ref, percentage] = useScrollPercentage({
/* Optional options */
threshold: 0,
})
return (
<div ref={ref}>
<h2>{`Percentage scrolled: ${percentage.toPrecision(2)}%.`}</h2>
</div>
)
}
To use the <ScrollPercentage>
component, you pass it a function. It will be
called whenever the user scrolls the viewport, with the new value of
percentage
. In addition to the percentage
, children also receives a ref
that should be set on the containing DOM element.
If you need it, you can also access the
IntersectionObserverEntry
on entry
, giving you access to all the details about the current intersection
state.
import { ScrollPercentage } from 'react-scroll-percentage'
const Component = () => (
<ScrollPercentage>
{({ percentage, ref, entry }) => (
<div ref={ref}>
<h2>{`Percentage scrolled: ${percentage.toPrecision(2)}%.`}</h2>
</div>
)}
</ScrollPercentage>
)
export default Component
You can pass any element to the <ScrollPercentage />
, and it will handle
creating the wrapping DOM element. Add a handler to the onChange
method, and
control the state in your own component. Any extra props you add the
<ScrollPercentage />
will be passed to the HTML element, allowing you set the
className
, style
, etc.
import { ScrollPercentage } from 'react-scroll-percentage'
const Component = () => (
<ScrollPercentage
as="div"
onChange={(percentage, entry) => console.log('Percentage:', percentage)}
>
<h2>Plain children are always rendered. Use onChange to monitor state.</h2>
</ScrollPercentage>
)
export default Component
⚠️ When rendering a plain child, make sure you keep your HTML output semantic. Change theas
to match the context, and add aclassName
to style the<ScrollPercentage />
. The component does not support Ref Forwarding, so if you need aref
to the HTML element, use the Render Props version instead.
Provide these as props on the <ScrollPercentage />
component and as the
options argument for the hooks.
Name | Type | Default | Required | Description |
---|---|---|---|---|
root | Element | window | false | The Element that is used as the viewport for checking visibility of the target. Defaults to the browser viewport (window ) if not specified or if null. |
rootMargin | string | '0px' | false | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). |
threshold | number | 0 | false | Number between 0 and 1 indicating the percentage that should be visible before triggering. |
triggerOnce | boolean | false | false | Only trigger this method once |
The <ScrollPercentage />
component also accepts the following props:
Name | Type | Default | Required | Description |
---|---|---|---|---|
as | string |
'div' | false | Render the wrapping element as this element. Defaults to div . |
children | ({ref, percentage, entry}) => React.ReactNode , ReactNode |
true | Children expects a function that receives an object containing the percentage boolean and a ref 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 the IntersectionObserverEntry as `entry, giving you more details. |
|
onChange | (percentage, entry) => void |
false | Call this function whenever the in view state changes. It will receive the percentage value, alongside the current IntersectionObserverEntry . |
Intersection Observer is the API is used to determine if an element is inside the viewport or not. Browser support is pretty good - With Safari adding support in 12.1, all major browsers now support Intersection Observers natively.
You can import the polyfill directly or use a service like polyfill.io to add it when needed.
yarn add intersection-observer
Then import it in your app:
import 'intersection-observer'
If you are using Webpack (or similar) you could use dynamic imports, to load the Polyfill only if needed. A basic implementation could look something like this:
/**
* Do feature detection, to figure out which polyfills needs to be imported.
**/
async function loadPolyfills() {
if (typeof window.IntersectionObserver === 'undefined') {
await import('intersection-observer')
}
}