useElementSizeMap
ericts-ui/use-element-size-mapFreeHook
A client-safe React hook that measures multiple keyed element sizes with ResizeObserver so animated shells can morph to active content.
Install it
npx shadcn@latest add https://ui.ericts.com/r/use-element-size-map.jsonSource
1"use client";23import { useCallback, useEffect, useRef, useState } from "react";45export type ElementSize = {6 width: number;7 height: number;8};910/**11 * Tracks the rendered size of multiple keyed elements with ResizeObserver.12 *13 * Use it when an animated shell, indicator, toolbar, or floating panel needs to14 * move toward the measured width/height of whichever keyed element is active.15 *16 * @example17 * const { setMeasureRef, sizes } = useElementSizeMap<HTMLDivElement>();18 * const activeSize = sizes[activeId];19 *20 * return (21 * <>22 * <div aria-hidden className="invisible absolute">23 * {items.map((item) => (24 * <div key={item.id} ref={setMeasureRef(item.id)}>25 * {item.content}26 * </div>27 * ))}28 * </div>29 * <motion.div animate={activeSize}>30 * {activeItem.content}31 * </motion.div>32 * </>33 * );34 */35export function useElementSizeMap<T extends HTMLElement>(threshold = 0.5) {36 const [sizes, setSizes] = useState<Record<string, ElementSize>>({});37 const observerRef = useRef<ResizeObserver | null>(null);38 const elements = useRef(new Map<string, T>());39 const idsByElement = useRef(new WeakMap<Element, string>());40 const callbacks = useRef(new Map<string, (node: T | null) => void>());41 const thresholdRef = useRef(threshold);4243 useEffect(() => {44 thresholdRef.current = threshold;45 }, [threshold]);4647 useEffect(() => {48 const activeElements = elements.current;4950 return () => {51 observerRef.current?.disconnect();52 observerRef.current = null;53 activeElements.clear();54 idsByElement.current = new WeakMap();55 };56 }, []);5758 const updateSizes = useCallback(59 (updates: ReadonlyArray<readonly [string, ElementSize]>) => {60 setSizes((current) => {61 let next = current;6263 for (const [id, nextSize] of updates) {64 const currentSize = current[id];6566 if (67 currentSize &&68 Math.abs(currentSize.width - nextSize.width) <=69 thresholdRef.current &&70 Math.abs(currentSize.height - nextSize.height) <=71 thresholdRef.current72 ) {73 continue;74 }7576 if (next === current) {77 next = { ...current };78 }7980 next[id] = nextSize;81 }8283 return next;84 });85 },86 [],87 );8889 const getObserver = useCallback(() => {90 if (typeof ResizeObserver === "undefined") return null;9192// … truncated
Files it writes
More from ericts-ui
All 38 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| useElementHeightuse-element-height | ericts-ui | Hooks | Free | no deps | |
| useReducedMotionuse-reduced-motion | ericts-ui | Hooks | Free | no deps | |
| useScrollAnchoruse-scroll-anchor | ericts-ui | Hooks | Free | no deps | |
| useScrollProgressuse-scroll-progress | ericts-ui | Hooks | Free | no deps |
