useMeasure

This commit is contained in:
lintuming 2019-08-23 18:53:41 +08:00
parent c73e92fbb5
commit fea782051e
5 changed files with 151 additions and 0 deletions

View File

@ -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
View 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)

View File

@ -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"

View 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
View 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;