diff --git a/README.md b/README.md
index 284f94e121..d1de6ae6c0 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@
- [`useMeasure`](./docs/useMeasure.md) and [`useSize`](./docs/useSize.md) — tracks an HTML element's dimensions. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemeasure--demo)
- [`createBreakpoint`](./docs/createBreakpoint.md) — tracks `innerWidth`
- [`useScrollbarWidth`](./docs/useScrollbarWidth.md) — detects browser's native scrollbars width. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usescrollbarwidth--demo)
+ - [`usePinchZoom`](./docs/usePinchZoom.md) — tracks pointer events to detect pinch zoom in and out status. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usePinchZoom--demo)
- [**UI**](./docs/UI.md)
diff --git a/docs/usePinchZoom.md b/docs/usePinchZoom.md
new file mode 100644
index 0000000000..212283d39e
--- /dev/null
+++ b/docs/usePinchZoom.md
@@ -0,0 +1,36 @@
+# `usePinchZoon`
+
+React sensor hook that tracks the changes in pointer touch events and detects value of pinch difference and tell if user is zooming in or out.
+
+## Usage
+
+```jsx
+import { usePinchZoon } from "react-use";
+
+const Demo = () => {
+ const [scale, setState] = useState(1);
+ const scaleRef = useRef();
+ const { zoomingState, pinchState } = usePinchZoom(scaleRef);
+
+ useEffect(() => {
+ if (zoomingState === "ZOOM_IN") {
+ // perform zoom in scaling
+ setState(scale + 0.1)
+ } else if (zoomingState === "ZOOM_OUT") {
+ // perform zoom out in scaling
+ setState(scale - 0.1)
+ }
+ }, [zoomingState]);
+
+ return (
+
+
![](https://www.olympus-imaging.co.in/content/000107506.jpg)
+
+ );
+};
+```
diff --git a/src/index.ts b/src/index.ts
index 4c8901a562..62b69356b7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -109,6 +109,7 @@ export { useMultiStateValidator } from './useMultiStateValidator';
export { default as useWindowScroll } from './useWindowScroll';
export { default as useWindowSize } from './useWindowSize';
export { default as useMeasure } from './useMeasure';
+export { default as usePinchZoom } from './usePinchZoom';
export { useRendersCount } from './useRendersCount';
export { useFirstMountState } from './useFirstMountState';
export { default as useSet } from './useSet';
diff --git a/src/usePinchZoom.ts b/src/usePinchZoom.ts
new file mode 100644
index 0000000000..330c2052d5
--- /dev/null
+++ b/src/usePinchZoom.ts
@@ -0,0 +1,109 @@
+import { RefObject, useEffect, useMemo, useState } from 'react';
+
+export type CacheRef = {
+ prevDiff: number;
+ evCache: Array;
+};
+
+export enum ZoomState {
+ 'ZOOMING_IN' = 'ZOOMING_IN',
+ 'ZOOMING_OUT' = 'ZOOMING_OUT',
+}
+
+export type ZoomStateType = ZoomState.ZOOMING_IN | ZoomState.ZOOMING_OUT;
+
+const usePinchZoom = (ref: RefObject) => {
+ const cacheRef = useMemo(
+ () => ({
+ evCache: [],
+ prevDiff: -1,
+ }),
+ [ref.current]
+ );
+
+ const [zoomingState, setZoomingState] = useState<[ZoomStateType, number]>();
+
+ const pointermove_handler = (ev: PointerEvent) => {
+ // This function implements a 2-pointer horizontal pinch/zoom gesture.
+ //
+ // If the distance between the two pointers has increased (zoom in),
+ // the target element's background is changed to 'pink' and if the
+ // distance is decreasing (zoom out), the color is changed to 'lightblue'.
+ //
+ // This function sets the target element's border to 'dashed' to visually
+ // indicate the pointer's target received a move event.
+ // Find this event in the cache and update its record with this event
+ for (let i = 0; i < cacheRef.evCache.length; i++) {
+ if (ev.pointerId == cacheRef.evCache[i].pointerId) {
+ cacheRef.evCache[i] = ev;
+ break;
+ }
+ }
+
+ // If two pointers are down, check for pinch gestures
+ if (cacheRef.evCache.length == 2) {
+ // console.log(prevDiff)
+ // Calculate the distance between the two pointers
+ const curDiff = Math.abs(cacheRef.evCache[0].clientX - cacheRef.evCache[1].clientX);
+
+ if (cacheRef.prevDiff > 0) {
+ if (curDiff > cacheRef.prevDiff) {
+ // The distance between the two pointers has increased
+ setZoomingState([ZoomState.ZOOMING_IN, curDiff]);
+ }
+ if (curDiff < cacheRef.prevDiff) {
+ // The distance between the two pointers has decreased
+ setZoomingState([ZoomState.ZOOMING_OUT, curDiff]);
+ }
+ }
+
+ // Cache the distance for the next move event
+ cacheRef.prevDiff = curDiff;
+ }
+ };
+
+ const pointerdown_handler = (ev: PointerEvent) => {
+ // The pointerdown event signals the start of a touch interaction.
+ // This event is cached to support 2-finger gestures
+ cacheRef.evCache.push(ev);
+ // console.log('pointerDown', ev);
+ };
+
+ const pointerup_handler = (ev: PointerEvent) => {
+ // Remove this pointer from the cache and reset the target's
+ // background and border
+ remove_event(ev);
+
+ // If the number of pointers down is less than two then reset diff tracker
+ if (cacheRef.evCache.length < 2) {
+ cacheRef.prevDiff = -1;
+ }
+ };
+
+ const remove_event = (ev: PointerEvent) => {
+ // Remove this event from the target's cache
+ for (let i = 0; i < cacheRef.evCache.length; i++) {
+ if (cacheRef.evCache[i].pointerId == ev.pointerId) {
+ cacheRef.evCache.splice(i, 1);
+ break;
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (ref?.current) {
+ ref.current.onpointerdown = pointerdown_handler;
+ ref.current.onpointermove = pointermove_handler;
+ ref.current.onpointerup = pointerup_handler;
+ ref.current.onpointercancel = pointerup_handler;
+ ref.current.onpointerout = pointerup_handler;
+ ref.current.onpointerleave = pointerup_handler;
+ }
+ }, [ref?.current]);
+
+ return zoomingState
+ ? { zoomingState: zoomingState[0], pinchState: zoomingState[1] }
+ : { zoomingState: null, pinchState: 0 };
+};
+
+export default usePinchZoom;
diff --git a/stories/usePinchZoom.story.tsx b/stories/usePinchZoom.story.tsx
new file mode 100644
index 0000000000..2394b96306
--- /dev/null
+++ b/stories/usePinchZoom.story.tsx
@@ -0,0 +1,37 @@
+import { storiesOf } from '@storybook/react';
+import { useEffect, useRef, useState } from 'react';
+import { usePinchZoom } from '../src';
+import { ZoomState } from '../src/usePinchZoom';
+import ShowDocs from './util/ShowDocs';
+
+const Demo = () => {
+ const [scale, setState] = useState(1);
+ const scaleRef = useRef();
+ const { zoomingState, pinchState } = usePinchZoom(scaleRef);
+
+ useEffect(() => {
+ if (zoomingState === ZoomState.ZOOMING_IN) {
+ // perform zoom in scaling
+ setState(scale + 0.1);
+ } else if (zoomingState === ZoomState.ZOOMING_OUT) {
+ // perform zoom out in scaling
+ setState(scale - 0.1);
+ }
+ }, [zoomingState, pinchState]);
+
+ return (
+
+
![scale img](https://www.olympus-imaging.co.in/content/000107506.jpg)
+
+ );
+};
+
+storiesOf('Sensors/usePinchZoom', module)
+ .add('Docs', () => )
+ .add('Default', () => );