import { useState, useCallback, useLayoutEffect } from "react"; export interface DimensionObject { width: number; height: number; top: number; left: number; x: number; y: number; right: number; bottom: number; } export const EmptyElementDimensions: DimensionObject = { bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, x: 0, y: 0, }; Object.freeze(EmptyElementDimensions); export type UseDimensionsHook = [ (node: HTMLElement) => void, DimensionObject, HTMLElement | null ]; export interface UseDimensionsArgs { liveMeasure?: boolean; } function getDimensionObject(node: HTMLElement): DimensionObject { const rect = node.getBoundingClientRect(); return { width: rect.width, height: rect.height, top: "x" in rect ? rect.x : rect.top, left: "y" in rect ? rect.y : rect.left, x: "x" in rect ? rect.x : rect.left, y: "y" in rect ? rect.y : rect.top, right: rect.right, bottom: rect.bottom, }; } function useDimensions({ liveMeasure = true, }: UseDimensionsArgs = {}): UseDimensionsHook { const [dimensions, setDimensions] = useState(EmptyElementDimensions); const [node, setNode] = useState(null); const ref = useCallback((el: HTMLElement) => { setNode(el); }, []); useLayoutEffect(() => { if (node) { const measure = () => window.requestAnimationFrame(() => setDimensions(getDimensionObject(node)) ); measure(); if (liveMeasure) { window.addEventListener("resize", measure); window.addEventListener("scroll", measure); return () => { window.removeEventListener("resize", measure); window.removeEventListener("scroll", measure); }; } } return () => { /* noop */ }; }, [liveMeasure, node]); return [ref, dimensions, node]; }