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