diff --git a/README.md b/README.md index 33956c69..386a0ea6 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ - [`useMedia`](./docs/useMedia.md) — tracks state of a CSS media query. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemedia--demo) - [`useMediaDevices`](./docs/useMediaDevices.md) — tracks state of connected hardware devices. - [`useMotion`](./docs/useMotion.md) — tracks state of device's motion sensor. + - [`useMouse`](./docs/useMouse.md) — tracks state of mouse position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemouse--docs) - [`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) — tracks some HTML element's scroll position. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usescroll--docs) diff --git a/docs/useMouse.md b/docs/useMouse.md new file mode 100644 index 00000000..66bd9b7d --- /dev/null +++ b/docs/useMouse.md @@ -0,0 +1,23 @@ +# `useMouse` + +React sensor hook that re-renders on mouse position changes. + +## Usage + +```jsx +import {useMouse} from 'react-use'; + +const Demo = () => { + const ref = React.useRef(null); + const {docX, docY, posX, posY, elX, elY, elW, elH} = useScroll(ref); + + return ( +
+
Mouse position in document - x:{docX} y:{docY}
+
Mouse position in element - x:{posX} y:{posY}
+
Element position - x:{elX} y:{elY}
+
Element dimensions - {elW}x{elH}
+
+ ); +}; +``` diff --git a/src/__stories__/useMouse.story.tsx b/src/__stories__/useMouse.story.tsx new file mode 100644 index 00000000..b699fea8 --- /dev/null +++ b/src/__stories__/useMouse.story.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import {storiesOf} from '@storybook/react'; +import {useMouse} from '..'; +import ShowDocs from '../util/ShowDocs'; + +const Demo = () => { + const ref = React.useRef(null); + const state = useMouse(ref); + + return ( + <> +
+        {JSON.stringify(state, null, 2)}
+      
+
+ Move your mouse over me +
+ + ); +}; + +storiesOf('Sensors|useMouse', module) + .add('Docs', () => ) + .add('Demo', () => + + ) diff --git a/src/index.ts b/src/index.ts index 456738ae..dad9bb57 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,7 @@ import useMedia from './useMedia'; import useMediaDevices from './useMediaDevices'; import useMotion from './useMotion'; import useMount from './useMount'; +import useMouse from './useMouse'; import useNetwork from './useNetwork'; import useNumber from './useNumber'; import useObservable from './useObservable'; @@ -84,6 +85,7 @@ export { useMediaDevices, useMotion, useMount, + useMouse, useNetwork, useNumber, useObservable, diff --git a/src/useMouse.ts b/src/useMouse.ts new file mode 100644 index 00000000..fa8a3dcb --- /dev/null +++ b/src/useMouse.ts @@ -0,0 +1,63 @@ +import {useState, useEffect, useRef, RefObject} from 'react'; + +export interface State { + docX: number; + docY: number; + posX: number; + posY: number; + elX: number; + elY: number; + elH: number; + elW: number; +} + +const useMouse = (ref: RefObject): State => { + const frame = useRef(0); + const [state, setState] = useState({ + docX: 0, + docY: 0, + posX: 0, + posY: 0, + elX: 0, + elY: 0, + elH: 0, + elW: 0, + }); + + useEffect(() => { + const handler = (event: MouseEvent) => { + frame.current = requestAnimationFrame(() => { + if (ref && ref.current) { + const {left, top} = ref.current.getBoundingClientRect() + const posX = left + window.scrollX; + const posY = top + window.scrollY; + + setState({ + docX: event.pageX, + docY: event.pageY, + posX, + posY, + elX: event.pageX - posX, + elY: event.pageY - posY, + elH: ref.current.offsetHeight, + elW: ref.current.offsetWidth, + }); + } + }); + } + + document.addEventListener('mousemove', handler); + + return () => { + if (frame.current) { + cancelAnimationFrame(frame.current); + } + + document.addEventListener('mousemove', handler); + }; + }, []); + + return state; +} + +export default useMouse