mirror of
https://github.com/streamich/react-use.git
synced 2026-01-25 14:17:16 +00:00
useMeasure
This commit is contained in:
parent
c73e92fbb5
commit
fea782051e
@ -64,6 +64,7 @@
|
||||
- [`useStartTyping`](./docs/useStartTyping.md) — detects when user starts typing.
|
||||
- [`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)
|
||||
- [`useMeasure`](./docs/useMeasure.md) — tracks an HTML element's dimensions by [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver)
|
||||
<br/>
|
||||
<br/>
|
||||
- [**UI**](./docs/UI.md)
|
||||
|
||||
23
docs/useMeasure.md
Normal file
23
docs/useMeasure.md
Normal file
@ -0,0 +1,23 @@
|
||||
# `useSize`
|
||||
|
||||
React sensor hook that tracks size of an HTML element.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import { useMeasure } from "react-use";
|
||||
|
||||
const Demo = () => {
|
||||
const [ref, { width, height }] = useSize();
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div>width: {width}</div>
|
||||
<div>height: {height}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
## Related hooks
|
||||
|
||||
- [useSize](./useSize.md)
|
||||
@ -50,6 +50,7 @@
|
||||
"nano-css": "^5.1.0",
|
||||
"react-fast-compare": "^2.0.4",
|
||||
"react-wait": "^0.3.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"screenfull": "^4.1.0",
|
||||
"throttle-debounce": "^2.0.1",
|
||||
"ts-easing": "^0.2.0"
|
||||
|
||||
84
src/__tests__/useMeasure.test.ts
Normal file
84
src/__tests__/useMeasure.test.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import useMeasure, { ContentRect } from '../useMeasure';
|
||||
|
||||
interface Entry {
|
||||
target: HTMLElement;
|
||||
contentRect: ContentRect;
|
||||
}
|
||||
|
||||
jest.mock('resize-observer-polyfill', () => {
|
||||
return class ResizeObserver {
|
||||
private cb: (entries: Entry[]) => void;
|
||||
private map: WeakMap<HTMLElement, any>;
|
||||
private targets: HTMLElement[];
|
||||
constructor(cb: () => void) {
|
||||
this.cb = cb;
|
||||
this.map = new WeakMap();
|
||||
this.targets = [];
|
||||
}
|
||||
public disconnect() {
|
||||
this.targets.map(target => {
|
||||
const originMethod = this.map.get(target);
|
||||
target.setAttribute = originMethod;
|
||||
this.map.delete(target);
|
||||
});
|
||||
}
|
||||
public observe(target: HTMLElement) {
|
||||
const method = 'setAttribute';
|
||||
const originMethod = target[method];
|
||||
this.map.set(target, originMethod);
|
||||
this.targets.push(target);
|
||||
target[method] = (...args) => {
|
||||
const [attrName, value] = args;
|
||||
if (attrName === 'style') {
|
||||
const rect: ContentRect = { top: 0, left: 0, right: 0, bottom: 0, width: 0, height: 0 };
|
||||
value.split(';').map(kv => {
|
||||
const [key, v] = kv.split(':');
|
||||
if (['top', 'bottom', 'left', 'right', 'width', 'height'].includes(key)) {
|
||||
rect[key] = parseInt(v, 10);
|
||||
}
|
||||
});
|
||||
target.getBoundingClientRect = () => rect;
|
||||
}
|
||||
originMethod.apply(target, args);
|
||||
this.fireCallback();
|
||||
};
|
||||
}
|
||||
private fireCallback() {
|
||||
if (this.cb) {
|
||||
this.cb(
|
||||
this.targets.map(target => {
|
||||
return {
|
||||
target,
|
||||
contentRect: target.getBoundingClientRect(),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
it('reacts to changes in size of any of the observed elements', () => {
|
||||
const { result } = renderHook(() => useMeasure());
|
||||
const div = document.createElement('div');
|
||||
result.current[0](div);
|
||||
expect(result.current[1]).toMatchObject({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
});
|
||||
act(() => div.setAttribute('style', 'width:200px;height:200px;top:100;left:100'));
|
||||
|
||||
expect(result.current[1]).toMatchObject({
|
||||
width: 200,
|
||||
height: 200,
|
||||
top: 100,
|
||||
bottom: 0,
|
||||
left: 100,
|
||||
right: 0,
|
||||
});
|
||||
});
|
||||
42
src/useMeasure.ts
Normal file
42
src/useMeasure.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
export interface ContentRect {
|
||||
width: number;
|
||||
height: number;
|
||||
top: number;
|
||||
right: number;
|
||||
left: number;
|
||||
bottom: number;
|
||||
}
|
||||
const useMeasure = <T>(): [(instance: T) => void, ContentRect] => {
|
||||
const [rect, set] = useState({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
});
|
||||
|
||||
const [observer] = useState(
|
||||
() =>
|
||||
new ResizeObserver(entries => {
|
||||
const entry = entries[0];
|
||||
set(entry.contentRect);
|
||||
})
|
||||
);
|
||||
|
||||
const ref = useCallback(
|
||||
node => {
|
||||
observer.disconnect();
|
||||
if (node) {
|
||||
observer.observe(node);
|
||||
}
|
||||
},
|
||||
[observer]
|
||||
);
|
||||
return [ref, rect];
|
||||
};
|
||||
|
||||
export default useMeasure;
|
||||
Loading…
x
Reference in New Issue
Block a user