diff --git a/README.md b/README.md index 8bf59aa..bc812fd 100644 --- a/README.md +++ b/README.md @@ -29,52 +29,86 @@ function MyComponent() { ## ResizeObserver -[Resize Observer](https://developers.google.com/web/updates/2016/10/resizeobserver) -is the API is used to determine if an element is resized. Browser support is pretty good in Chrome, but is still missing support in other major browsers. +If it is present, this library uses the recent [`ResizeObserver` browser +API](https://developers.google.com/web/updates/2016/10/resizeobserver) to +determine if an element's content size has changed. + +If a browser does not have the `ResizeObserver` API present, then this library +falls back to listening on window size changes, which is less efficient and does +not listen for changes to the component's size due to other factors like content +changes. If it is not present, you can use pass a `ResizeObserver` +implementation into the `useComponentSize()` hook (see below). + +Browser support is pretty good in Chrome, but is still missing support in other +major browsers. > [Can i use ResizeObserver?](https://caniuse.com/#feat=resizeobserver) ### Polyfill -You can import the -[polyfill](https://github.com/que-etc/resize-observer-polyfill) directly from here +You can import [a ResizeObserver +ponyfill](https://github.com/que-etc/resize-observer-polyfill) with this NPM +library: ```sh yarn add resize-observer-polyfill ``` -Then import it in your app: +Then use it with the `useComponentSize()` hook: ```js -import 'resize-observer-polyfill' +import ResizeObserver from 'resize-observer-polyfill' +// ... +useComponentSize(ref, { ResizeObserver }); ``` If you are using Webpack (or similar) you could use [dynamic -imports](https://webpack.js.org/api/module-methods/#import-), to load the -Polyfill only if needed. A basic implementation could look something like this: +imports](https://webpack.js.org/api/module-methods/#import), to load the +Polyfill only if needed. A basic implementation in your component could look +something like this: ```js -loadPolyfills() - .then(() => /* Render React application now that your Polyfills are ready */) - -/** -* Do feature detection, to figure out which polyfills needs to be imported. -**/ -function loadPolyfills() { - const polyfills = [] - - if (!supportsResizeObserver()) { - polyfills.push(import('resize-observer-polyfill')) +// GetResizeObserver.js +// Ponyfill, not polyfill, since we're not chaging the global +// `window.ResizeObserver` variable +let ResizeObserverPonyfill; + +export async function getResizeObserverOrPonyfill() { + const BuiltinResizeObserver = window.ResizeObserver; + if (BuiltinResizeObserver) { + return BuiltinResizeObserver; } - - return Promise.all(polyfills) + ResizeObserverPonyfill = (await import("resize-observer-polyfill")).default; + return ResizeObserverPonyfill; } -function supportsResizeObserver() { - return ( - 'ResizeObserver' in global && - 'ResizeObserverEntry' in global && - 'contentRect' in ResizeObserverEntry.prototype - ) -} +``` + +```js +// Your component +const [ResizeObserver, setResizeObserver] = useState( + window.ResizeObserver); + +useEffect(() => { + if (ResizeObserver) { + return; // don't need to load the ponyfill + } + let cancelled = false; + + // if imported multiple times, should load from browser cache; + // or you can cache this in a variable + getResizeObserverOrPonyfill().then(lib => { + if (!cancelled) { + // must wrap `lib` in a function: `ResizeObserver` is a function + // itself, so prevent the state hook from interpreting as a reducer + setResizeObserver(() => lib); + } + }); + + return () => { + // if unmounted before complete, don't call set state + cancelled = true; + } +}, []); +useComponentSize(ref, { ResizeObserver }); ``` diff --git a/index.d.ts b/index.d.ts index 8d655d4..107026c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,4 +3,8 @@ interface ComponentSize { height: number } -export default function useComponentSize(ref: React.RefObject): ComponentSize +interface ComponentSizeOptions { + ResizeObserver?: ResizeObserver; +} + +export default function useComponentSize(ref: React.RefObject, opts?: ComponentSizeOptions): ComponentSize diff --git a/index.js b/index.js index 5e652a2..9f5f7ca 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,12 @@ function getSize(el) { } } -function useComponentSize(ref) { +function useComponentSize(ref, opts) { + var ResizeObserverConstructor = opts && opts.ResizeObserver + ? opts.ResizeObserver + : typeof ResizeObserver === 'function' + ? ResizeObserver + : undefined; var _useState = useState(getSize(ref ? ref.current : {})) var ComponentSize = _useState[0] var setComponentSize = _useState[1] @@ -40,8 +45,8 @@ function useComponentSize(ref) { handleResize() - if (typeof ResizeObserver === 'function') { - var resizeObserver = new ResizeObserver(function() { + if (ResizeObserverConstructor) { + var resizeObserver = new ResizeObserverConstructor(function() { handleResize() }) resizeObserver.observe(ref.current) @@ -58,7 +63,7 @@ function useComponentSize(ref) { } } }, - [ref.current] + [ref.current, ResizeObserverConstructor] ) return ComponentSize diff --git a/index.js.flow b/index.js.flow index 18dc8a8..e350dfe 100644 --- a/index.js.flow +++ b/index.js.flow @@ -3,4 +3,8 @@ interface ComponentSize { height: number, } -declare export default function useComponentSize(ref: React.Ref): ComponentSize; \ No newline at end of file +interface ComponentSizeOptions { + ResizeObserver?: ResizeObserver; +} + +declare export default function useComponentSize(ref: React.Ref, opts?: ComponentSizeOptions): ComponentSize; \ No newline at end of file diff --git a/package.json b/package.json index cd856df..082c013 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "raf": "3.4.0", "react": "16.8.1", "react-dom": "16.8.1", - "react-test-renderer": "16.8.1" + "react-test-renderer": "16.8.1", + "resize-observer-polyfill": "^1.5.1" }, "ava": { "require": [