一、背景
考虑这样一种情况,产品同学希望达到以下功能:
在我们的网页中有一个固定区域,这个区域会用于渲染从后端拉取的含有图片等资源的富文本字符串。
它需要在内容不超过一个最大高度的时候完全显示所有内容,超过最大内容后仅展示最大高度范围内的内容,超出部分隐藏,并通过一个按钮 “展示更多” 来给用户展示更多的选择。
五、监听所有资源的 onload 事件
既然上述方法都不行,那么我绞尽脑汁,又想出了另外一种方法:监听所有带有 src 属性的 DOM 元素的 onload 事件,通过他的回调来判断当前容器的高度情况
这种实现方式,在思路上是完全符合目的的,具体做法参考如下:
const [height, setHeight] = useState(-1); const [showMore, setShowMore] = useState(false); // contentRef 的定义见 MutationObserver 一节 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); }; }, []);
通过这种方式可以实现对富文本中的图片进行加载后,对容器高度进行相应的判断。
但是这种方式,存在不确定性,即无法判断是否找齐了所有高度由内容撑开的资源。
六、Iframe
这是终极方案,也是在此背景中所采用的方案。
既然 window 可以监听到 resize
事件,那么我们就可以利用 iframe 来达到同样的效果,具体做法就是在容器里面嵌套一个隐藏的高度为 100% 的 iframe,通过监听他的 resize
事件,来判断当前容器的高度。
话不多说,具体实现方式如下:
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 isFolded = maxHeight === MAX_HEIGHT; 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) returnnull; return ( <section className="section detail-content"> <div className="content-wrapper"> <div className="content" dangerouslySetInnerHTML={{ __html: details }} style={{ maxHeight }} ref={ref} /> {/* 这个 iframe 是用来动态监听 content 高度的变化的 */} <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> ); };
这种方式实际上就是对 ResizeObserver
的一种 hack,经过多次实践,符合功能要求。
七、总结
- 解决问题要尽可能的考虑多种情况,对比多种方案,采取最为可靠的一种方案。
- 不断学习,多查询资料,你所遇到的问题基本上前人都已经踩过坑了。
- 监听 DOM 元素的高度变化,可以采用内嵌 iframe 的方式来解决。
相关阅读:Vue 实现文本溢出展开收起详情功能
文章来源:腾讯 IMWeb 前端团队 ,作者 hope