remeasure
Version:
Get position and size of the DOM element for any React Component
361 lines (286 loc) • 9.35 kB
Markdown
# remeasure
<img src="https://img.shields.io/badge/build-passing-brightgreen.svg"/>
<img src="https://img.shields.io/badge/coverage-100%25-brightgreen.svg"/>
<img src="https://img.shields.io/badge/license-MIT-blue.svg"/>
Get position and size of the DOM element for any React Component
## Table of contents
- [Usage](#usage)
- [As a decorator](#as-a-decorator)
- [As a component](#as-a-component)
- [Measurements](#measurements)
- [Advanced usage](#advanced-usage)
- [keys](#keys)
- [options](#options)
- [ref](#ref)
- [Convenience methods](#convenience-methods)
- [Caveats](#caveats)
- [Support](#support)
- [Development](#development)
## Usage
```javascript
// ES2015
import { measure, Measured } from "remeasure";
// CommonJS
const { measure, Measured } = require("remeasure").default;
// old school script
var measure = window.Remeasure.measure;
var Measured = window.Remeasure.Measured;
```
#### As a decorator
```javascript
@measure
class MyComponent extends React.Component {
render() {
const { height, width } = this.props;
return <div>I have access to my height and width through props!</div>;
}
}
const StatelessComponent = measure(({ height, width }) => {
return <div>In here too!</div>;
});
```
#### As a component
You can use the component with function-rendering components one of three ways:
```javascript
// using a children method
const MyMeasuredComponent = () => {
return (
<Measured height width>
{({ height, width }) => {
return (
<div>
My height is {height} and my width is {width}
</div>
);
}}
</Measured>
);
};
// using a component method
const MyMeasuredComponent = () => {
return (
<Measured
height
width
component={({ height, width }) => {
return (
<div>
My height is {height} and my width is {width}
</div>
);
}}
/>
);
};
// using a render method
const MyMeasuredComponent = () => {
return (
<Measured
height
width
render={({ height, width }) => {
return (
<div>
My height is {height} and my width is {width}
</div>
);
}}
/>
);
};
```
For performance reasons, it is recommended that you store the component as a separate method rather than create a method inline:
```javascript
// using a render method
const Render = ({ height, width }) => {
return (
<div>
My height is {height} and my width is {width}
</div>
);
};
const MyMeasuredComponent = () => {
return <Measured height width render={Render} />;
};
```
#### Measurements
The following properties are available for measurement:
```javascript
{
bottom: Number,
clientLeft: Number,
clientHeight: Number,
clientWidth: Number,
clientTop: Number,
height: Number,
left: Number,
naturalHeight: Number,
naturalWidth: Number,
offsetHeight: Number,
offsetLeft: Number,
offsetTop: Number,
offsetWidth: Number,
scrollHeight: Number,
scrollLeft: Number,
scrollTop: Number,
scrollWidth: Number,
right: Number,
top: Number
width: Number
}
```
The `bottom`, `left`, `right`, and `top` properties are what you would expect from the result of `element.getBoundingClientRect()`. `naturalHeight` and `naturalWidth` are properties that are native to `img` elements, and for all non-`img` elements they are coalesced with `scrollHeight` and `scrollWidth`, respectively.
These properties are retrieved on mount, but will also automatically update if the element is resized thanks to [ResizeObserver](https://github.com/que-etc/resize-observer-polyfill). Please note that elements that do not support content (such as `img`) are not supported by this resize listener because there is no content box to observe. If you need to support those elements, simply create a higher-order component that wraps that element in a `div` and decorate that component.
## Advanced usage
#### keys
`(Array<string>|string)`
The keys to listen for changes to. If not specified, all possible keys will be measured.
Examples:
```javascript
import measure from "remeasure";
// pass a string value for a single property
const measureOnlyOffsetWidth = measure("offsetWidth");
const MyStatelessComponent = measureOnlyOffsetWidth(({ offsetWidth }) => {
return <div>Only offsetWidth is injected</div>;
});
// or an array of string values for multiple properties
@measure(["top", "height"])
class MyComponent extends Component {
render() {
const { top, height } = this.props;
return <div>Both the top and height props are injected</div>;
}
}
```
You can apply the keys one of two ways on the `Measure` component:
```javascript
// either as individual boolean properties
<Measured height>
{({height}) => {
return (
<div>I am {height} pixels in height.</div>
);
}}
</Measured>
// or as the "keys" prop
<Measured keys={['height']}>
{({height}) => {
return (
<div>I am {height} pixels in height.</div>
);
}}
</Measured>
```
Note that the properties will only be applied if they are set to `true` (yes, you can actually toggle what properties are measured!).
#### options
`Object`
Allows customization of the measurements. Available options:
```javascript
{
// value in milliseconds to debounce rerenders
debounce: Number,
// sets namespace for values to be passed into props on
namespace: String,
// should element rerender when resized
renderOnResize: Boolean = true
// should element rerender when the window is resized
renderOnWindowResize: Boolean = false
}
```
Example usage with the decorator:
```javascript
// use them alone
@measure({ renderOnResize: false })
class MyComponent extends Component {
render() {
const { height, width } = this.props;
return <div>The height and width props will not update with resizes.</div>;
}
}
// or you can use them with keys
const MyStatelessComponent = measure(["height", "width"], {
debounce: 50,
namespace: "measurements"
})(({ measurements }) => {
return (
<div>
You can still pass options when you want to specify keys, as the second
parameter.
</div>
);
});
```
Example usage with the `Measured` component:
```javascript
<Measured debounce={500} namespace="measurements">
{({ measurements }) => {
return <div>My measurements: {JSON.stringify(measurements)}</div>;
}}
</Measured>
```
#### ref
Like any other component, you can access the `Measured` component instance via the `ref`, but when using the `measure` decorator you will be accessing the `Measured` HOC and not the original component. If you want to access the original component, it is available as the `originalComponent` property on that `ref`.
```javascript
@measure.width
class Foo extends Component {
getProps() {
return this.props;
}
render() {
return <div>Use getProps to get my props!</div>;
}
}
...
class FooConsumer extends Component {
componentDidMount() {
console.log(this.foo); // Measured component
console.log(this.foo.originalComponent); // Foo component
console.log(this.foo.originalComponent.getProps()); // {bar: 'bar'}
}
render() {
<Foo
ref={(component) => {
this.foo = component;
}}
bar="bar"
/>
}
}
```
## Convenience methods
For each key that is measured, a convenience function exists on the `measure` decorator. Example:
```javascript
@measure.width
class MyMeasuredComponent extends Component {
render() {
const { width } = this.props;
return <div>I have width of {width}.</div>;
}
}
```
## Caveats
A couple things to keep in mind when using `remeasure`:
**Void tags cannot detect element resize**
If children on a tag are considered invalid HTML (such as for `<input/>`, `<img/>`, etc), then the internal element resize detector cannot work. The easy solution to this is to update the component via props (on update, a recalculation of values is triggered).
**Components may render twice on update**
If you perform an update to the component `props` or `state` that also happens to change its dimensions, the component will update twice, once for the changes to `props` / `state`, and again for the changes to its dimensions. This is because the component needs to render in the DOM before updated values can be calculated.
## Support
`remeasure` has been tested and confirmed to work on the following browsers:
- Chrome
- Firefox
- Opera
- Edge
- IE9+
`remeasure` also works with universal / isomorphic applications.
## Development
Standard stuff, clone the repo and `npm i` to get the dependencies. npm scripts available:
- `build` => builds the distributed JS with `NODE_ENV=development` and with sourcemaps
- `build-minified` => builds the distributed JS with `NODE_ENV=production` and minified
- `compile-for-publish` => runs the `lint`, `test`, `transpile`, `dist` scripts
- `dev` => runs the webpack dev server for the playground
- `dist` => runs the `build` and `build-minified`
- `lint` => runs ESLint against files in the `src` folder
- `prepublish` => if in publish, runs `compile-for-publish`
- `test` => run ava with NODE_ENV=test
- `test:watch` => runs `test` but with persistent watcher
- `transpile` => runs Babel against files in `src` to files in `lib`