Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | 67x 2954x 1310x 1310x 1204x 1502x 1502x 1502x 1477x 1204x 1204x 1204x 1204x 1477x 1477x 1477x 1477x 1477x 1465x 1465x 1477x 1389x 1389x 1502x | import { useRef, useState, useEffect, RefObject } from 'react';
const _getMovementIntersectionObserver = ({
callback,
rootMargin,
threshold,
}: {
callback: () => void;
rootMargin: string;
threshold: number[];
}): IntersectionObserver => {
return new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
callback();
}
});
},
{
threshold,
rootMargin,
}
);
};
/**
* Calculates the maximum height for an element based on its position
* relative to the bottom of the viewport.
*
* @param data The data that, when changed, should trigger a recalculation.
* @param buffer Optional buffer space (in pixels) to leave below the element. Defaults to 20.
* @param minHeight Optional minimum height (in pixels) for the element. Defaults to 100.
* @returns An object containing:
* - `ref`: A RefObject to attach to the target DOM element.
* - `maxHeight`: The calculated maximum height string (e.g., "500px").
*/
export function useDynamicMaxHeight(
data: any,
buffer = 20,
minHeight = 100
): {
ref: RefObject<HTMLDivElement>;
maxHeight: string;
} {
const ref = useRef<HTMLDivElement>(null);
const [maxHeight, setMaxHeight] = useState<string>('100vh'); // Start with full viewport height initially
useEffect(() => {
const calculateMaxHeight = () => {
if (ref.current) {
const rect = ref.current.getBoundingClientRect();
const availableHeight = window.innerHeight - rect.top - buffer;
setMaxHeight(`${Math.max(minHeight, availableHeight)}px`);
}
};
// Two intersection observers to trigger a recalculation when the target element
// moves up or down. One for moving up and one for moving down.
// Note that with this approach we don't need to use a resize observer nor
// a window resize listener.
// The trick is to use a margin for the IntersectionObserver to detect movement.
// See more below.
const rootMarginHeight = maxHeight === '100vh' ? `${window.innerHeight}px` : `${maxHeight}`;
// Note that we use a fine grained threshold because we don't know how
// much it will move and we want any movement to trigger the intersection observer.
const threshold = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
// The trick here is to use the calculated maxHeight as the root margin height
// so that any movement of the target element down (i.e. "out of the" viewport)
// will trigger the intersection observer.
const moveDownIntersectionObserver = _getMovementIntersectionObserver({
callback: calculateMaxHeight,
rootMargin: `0px 0px ${rootMarginHeight} 0px`,
threshold,
});
// The trick here is to use the calculated maxHeight as the negative
// root margin height so that any movement of the target element up
// (i.e. "into the" viewport) will trigger the intersection observer.
const moveUpIntersectionObserver = _getMovementIntersectionObserver({
callback: calculateMaxHeight,
rootMargin: `0px 0px -${rootMarginHeight} 0px`,
threshold,
});
if (ref.current) {
moveUpIntersectionObserver.observe(ref.current);
moveDownIntersectionObserver.observe(ref.current);
}
// Cleanup listener and requestAnimationFrame on component unmount
return () => {
moveUpIntersectionObserver.disconnect();
moveDownIntersectionObserver.disconnect();
};
// Dependencies: buffer, minHeight, and data.
}, [data, buffer, minHeight, maxHeight]);
return { ref, maxHeight };
}
export default useDynamicMaxHeight;
|