Skip to content

Instantly share code, notes, and snippets.

@curran
Forked from AdamMcCormick/ScrollBarAdapter.jsx
Last active April 11, 2021 23:27
Show Gist options
  • Save curran/0e30c621fe4fc612bf7feb0938a68e4d to your computer and use it in GitHub Desktop.
Save curran/0e30c621fe4fc612bf7feb0938a68e4d to your computer and use it in GitHub Desktop.
A hook to listen for width changes or scroll-bar show/hide.
import { useEffect } from 'react';
/**
* A hook to listen for width changes or scroll-bar show/hide.
*
* Arguments:
* * containerRef - a React ref to the element whose width you want to measure.
* * onWidthChanged - a function that is invoked when the width changes.
*
* Based on https://gist.github.com/AdamMcCormick/d5f718d2e9569acdf7def25e8266bb2a
*/
export const useWidthDetector = (containerRef, onWidthChanged) => {
useEffect(() => {
if (containerRef) {
const detector = document.createElement('iframe');
Object.assign(detector.style, {
height: 0,
border: 0,
width: '100%'
});
const container = containerRef.current;
container.appendChild(detector);
// Invoke here to capture initial width.
onWidthChanged();
// Detect when width changes hereafter.
detector.contentWindow.addEventListener('resize', onWidthChanged);
return () => {
detector.contentWindow.removeEventListener('resize', onWidthChanged);
container.removeChild(detector);
};
}
}, [containerRef, onWidthChanged]);
};
@aquaductape
Copy link

There's still a noticeable height, you need to add display: block on the style property object. iframe is an inline element, so it will create a "line box" which it's height will be set the same pixels as line height.

@finnmerlett
Copy link

finnmerlett commented Mar 9, 2021

This is really excellent - with the suggested style change it works like an absolute charm. I added the style change display: block; and an enable option (as you can't conditionally call a hook, and I needed to be able to switch it off). I also made it Typescript-compliant - if anyone is interested, here is my updated version:

import { RefObject, useEffect } from 'react';

type Func<P extends unknown[] = any[], R = unknown> = (
  ...args: P
) => R;

/**
 * A hook to listen for width changes or scroll-bar show/hide.
 *
 * Arguments:
 *  * containerRef - a React ref to the element whose width you want to measure.
 *  * onWidthChanged - a function that is invoked when the width changes.
 *
 * Based on https://gist.github.com/AdamMcCormick/d5f718d2e9569acdf7def25e8266bb2a
 */
export default function useWidthDetector(
  containerRef: RefObject<HTMLDivElement>,
  onWidthChanged: Func,
  { enabled } = { enabled: true }
): void {
  useEffect(() => {
    if (containerRef.current && enabled) {
      const detector = document.createElement('iframe');
      Object.assign(detector.style, {
        height: 0,
        border: 0,
        width: '100%',
        display: 'block',
      });

      const container = containerRef.current;
      container.appendChild(detector);

      if (!detector.contentWindow) {
        throw new Error('No content window found on iFrame');
      }
      const detectorWindow = detector.contentWindow;

      // Invoke here to capture initial width.
      onWidthChanged();

      // Detect when width changes hereafter.
      detectorWindow.addEventListener('resize', onWidthChanged);

      return () => {
        detectorWindow?.removeEventListener('resize', onWidthChanged);
        container.removeChild(detector);
      };
    }
  }, [containerRef, enabled, onWidthChanged]);
}

@curran
Copy link
Author

curran commented Mar 10, 2021

Very nice!

@finnmerlett
Copy link

I cannot believe I didn't find this before. Waaaay simpler - https://github.com/maslianok/react-resize-detector

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment