diff --git a/README.md b/README.md index 569c82e26c..494daf12a1 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ - [`useMotion`](./docs/useMotion.md) — tracks state of device's motion sensor. - [`useNetwork`](./docs/useNetwork.md) — tracks state of user's internet connection. - [`useOrientation`](./docs/useOrientation.md) — tracks state of device's screen orientation. + - [`useScroll`](./docs/useScroll.md) — some HTML element's scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usescroll--docs) + - [`useSize`](./docs/useSize.md) — tracks some HTML element's dimensions. - [`useWindowScroll`](./docs/useWindowScroll.md) — tracks `Window` scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usewindowscroll--docs) - [`useWindowSize`](./docs/useWindowSize.md) — tracks `Window` dimensions. [![][img-demo]](https://codesandbox.io/s/m7ln22668) diff --git a/docs/useScroll.md b/docs/useScroll.md new file mode 100644 index 0000000000..6de5730795 --- /dev/null +++ b/docs/useScroll.md @@ -0,0 +1,21 @@ +# `useScroll` + +React sensor hook that re-renders on when scroll position in a DOM element changes. + +## Usage + +```jsx +import {useScroll} from 'react-use'; + +const Demo = () => { + const element = React.useRef(null); + const {x, y} = useScroll(element); + + return ( +
+
x: {x}
+
y: {y}
+
+ ); +}; +``` diff --git a/src/__stories__/useScroll.story.tsx b/src/__stories__/useScroll.story.tsx new file mode 100644 index 0000000000..db0c2546fd --- /dev/null +++ b/src/__stories__/useScroll.story.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import {storiesOf} from '@storybook/react'; +import {useScroll} from '..'; +import ShowDocs from '../util/ShowDocs'; + +const Demo = () => { + const element = React.useRef(null); + const {x, y} = useScroll(element); + + return ( + <> +
x: {x}
+
y: {y}
+
+ +
+ Scroll me +
+
+ + ); +}; + +storiesOf('Sensors/useScroll', module) + .add('Docs', () => ) + .add('Demo', () => + + ) diff --git a/src/__stories__/useWindowScroll.story.tsx b/src/__stories__/useWindowScroll.story.tsx index 8b84475f1c..cce99a3ae9 100644 --- a/src/__stories__/useWindowScroll.story.tsx +++ b/src/__stories__/useWindowScroll.story.tsx @@ -8,14 +8,14 @@ const Demo = () => { return (
+ right: 0 + }}>
x: {x}
y: {y}
diff --git a/src/index.ts b/src/index.ts index 514af8dbdb..ba0298d64d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,6 +35,7 @@ import useOutsideClick from './useOutsideClick'; import usePromise from './usePromise'; import useRaf from './useRaf'; import useRefMounted from './useRefMounted'; +import useScroll from './useScroll'; import useSessionStorage from './useSessionStorage'; import useSetState from './useSetState'; import useSize from './useSize'; @@ -90,6 +91,7 @@ export { usePromise, useRaf, useRefMounted, + useScroll, useSessionStorage, useSetState, useSize, diff --git a/src/useScroll.ts b/src/useScroll.ts new file mode 100644 index 0000000000..dcbdc99834 --- /dev/null +++ b/src/useScroll.ts @@ -0,0 +1,48 @@ +import {useState, useEffect, useRef} from 'react'; +import {isClient} from './util'; + +export interface State { + x: number; + y: number; +} + +const useScroll = (ref): State => { + const frame = useRef(0); + const [state, setState] = useState({ + x: isClient ? window.scrollX : 0, + y: isClient ? window.scrollY : 0 + }); + + useEffect(() => { + const handler = () => { + + frame.current = requestAnimationFrame(() => { + setState({ + x: ref.current.scrollLeft, + y: ref.current.scrollTop + }); + }); + } + + if (ref && ref.current) { + ref.current.addEventListener('scroll', handler, { + capture: false, + passive: true + }); + } + + return () => { + if (frame.current) { + cancelAnimationFrame(frame.current); + } + + if (ref && ref.current) { + ref.current.removeEventListener('scroll', handler); + } + }; + }, [ref]); + + return state; +} + +export default useScroll