Detecting Dynamic Content Height for a “Show More” Button in React
This article explores several techniques—including MutationObserver, IntersectionObserver, ResizeObserver, onload listeners, and an iframe hack—to reliably detect when a rich‑text container with images exceeds a maximum height so a "show more" button can be displayed.
1. Background
Product designers need a fixed area on a web page that renders rich‑text (including images) fetched from the backend. The area should display all content when its height is below a maximum value, but hide overflow and show a "Show More" button when the content exceeds that height.
The difficulty lies in dynamically detecting height changes because images load asynchronously, causing the browser to re‑flow the layout at unknown times.
Attempts made:
MutationObserver
IntersectionObserver
ResizeObserver
Listening to all resources'
onloadevents
Embedding an
iframe2. MutationObserver
MutationObserver provides the ability to watch for changes to the DOM tree.
observe(target, options)
The API can observe a single node or its descendants based on the supplied options.
<code>const contentRef = useRef();</code> <code>const [height, setHeight] = useState(-1);
const [observer, setObserver] = useState<MutationObserver>(null!);
useEffect(() => {
const observer = new MutationObserver((mutationList) => {
if (height !== contentRef.current?.clientHeight) {
console.log("Height changed!");
setHeight(contentRef.current.clientHeight);
}
});
setObserver(observer);
}, []);
</code> <code>useEffect(() => {
if (!observer || !contentRef.current) return;
observer.observe(contentRef.current, {
childList: true,
attributes: true,
characterData: true,
subtree: true,
});
}, [contentRef.current, observer]);
</code>The approach fails because the CSS
maxHeightproperty never changes; the browser computes the height automatically after images load, so the observer does not detect the needed change.
Browser compatibility for MutationObserver is acceptable.
3. IntersectionObserver
After discovering MutationObserver cannot meet the requirement, the author tried IntersectionObserver, which can detect whether an element enters the viewport.
The API provides an
intersectionRatioproperty. The idea was to show the "Show More" button when the ratio equals 1, indicating the element is fully visible.
In practice the ratio is unreliable; an element can be fully visible but the ratio is not exactly 1, so the approach was abandoned.
4. ResizeObserver
ResizeObserver is designed to listen for DOM size changes, but it is still experimental and has poor browser support, making it unsuitable for production.
Reference article: "Detecting DOM size changes with ResizeObserver".
5. Listening to all resources' onload events
Another approach is to attach
onloadhandlers to every element with a
srcattribute, then check the container height after each load.
<code>const [height, setHeight] = useState(-1);
const [showMore, setShowMore] = useState(false);
useEffect(() => {
const sources = contentRef.current.querySelectorAll("[src]");
sources.onload = () => {
const height = contentRef?.current?.clientHeight ?? 0;
const show = height >= parseInt(MAX_HEIGHT, 10);
setHeight(height);
setShowMore(show);
};
}, []);
</code>This works for images but cannot guarantee that all resources affecting height have loaded, leading to uncertainty.
6. Iframe (final solution)
The ultimate solution embeds a hidden
iframewith 100% height inside the container. By listening to the iframe's
resizeevent, the container height can be detected reliably.
<code>const Detail: FC<{}> = () => {
const ref = useRef<HTMLDivElement>(null);
const ifr = useRef<HTMLIFrameElement>(null);
const [height, setHeight] = useState(-1);
const [showMore, setShowMore] = useState(false);
const [maxHeight, setMaxHeight] = useState(MAX_HEIGHT);
const introduceInfo = useAppSelect(state => state.courseInfo?.data?.introduce_info ?? {});
const details = introduceInfo.details ?? "";
const onresize = useCallback(() => {
const height = ref?.current?.clientHeight ?? 0;
const show = height >= parseInt(MAX_HEIGHT, 10);
setHeight(height);
setShowMore(show);
if (ifr.current && show) {
ifr.current.remove();
}
}, []);
useEffect(() => {
if (!ref.current || !ifr.current?.contentWindow) return;
ifr.current.contentWindow.onresize = onresize;
onresize();
}, [details]);
if (!details) return null;
return (
<section className="section detail-content">
<div className="content-wrapper">
<div className="content" dangerouslySetInnerHTML={{ __html: details }} style={{ maxHeight }} ref={ref} />
{/* hidden iframe for height monitoring */}
<iframe title={IFRAME_ID} id={IFRAME_ID} ref={ifr} />
</div>
{isFolded && showMore && (
<>
<div className="show-more" onClick={() => setMaxHeight(isFolded ? "none" : MAX_HEIGHT)}>
查看全部
<IconArrowDown className="icon" />
</div>
<div className="mask" />
</>
)}
</section>
);
};
</code>This iframe‑based hack effectively monitors height changes and satisfies the functional requirements.
7. Summary
Consider multiple approaches and choose the most reliable one.
Continuously learn and consult existing resources; many problems have been solved before.
Embedding an iframe is a practical way to listen for DOM element height changes.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.