项目中遇到这样的情况,需要用到iframe,iframe中的内容也是自己写的页面,由于页面中元素是异步加载出来的,并不能提前预知其高度,这样就不能设置iframe的高度,导致iframe会出现滚动条,用户体验不好。所以我需要能根据内容动态改变iframe的高度。
dom结构如下
<!-- iframe -->
<iframe id="iframe" scrolling="no" src="./iframe/page"></iframe>
<!-- ./iframe/page页 -->
<div id="main">
// 异步加载的dom
</div>
一、setInterval
利用 setInterval 循环计算内容高度,然后给iframe重新设置,这样有缺点,无法得知什么时候才加载完毕,定时器需要一直执行,虽然对性能影响很小,但是不建议这样写。
setInterval(() => {
const iframe = window.frameElement;
const main= iframe?.contentWindow?.document?.querySelector('#main');
iframe.height = main?.scrollHeight + 10;
}, 200)
二、DOMNodeInserted
DOMNodeInserted事件是监听dom节点中有新增节点,当内容有新元素插入时,重新设置iframe高度,如果dom结构过于复杂,事件会频繁触发,此时可以配合防抖函数,设置时间为200ms
import debounce from 'lodash/debounce';
// ...
document.querySelector('#main).addEventListener('DOMNodeInserted', debounce(resize, 200), false);
测试过程中发现,页面中第三方插件包含canvas,而且canvas绘制比较缓慢,会偶然出现canvas不能完整显示的情况。具体原因未知,怀疑是canvas绘制过程中修改了大小。
三、ResizeObserver
ResizeObserver
接口可以监听到Element
的内容区域或SVGElement
的边界框改变。内容区域则需要减去内边距padding。(有关内容区域、内边距资料见盒子模型 )ResizeObserver避免了在自身回调中调整大小,从而触发的无限回调和循环依赖。它仅通过在后续帧中处理DOM中更深层次的元素来实现这一点。如果(浏览器)遵循规范,只会在绘制前或布局后触发调用。
最后发现ResizeObserver,这是一个实验中的api,但是根据MDN的介绍,兼容性满足用户群体的日常使用。
ResizeObserver可以监听dom大小变化,正适合这种场景下使用,具体api的使用方法这里就不赘述了,可以参考其他资料。下面是我最后的逻辑:
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
resize(entry.contentRect.height);
}
});
resizeObserver.observe(document.querySelector('#main'));
return () => {
resizeObserver.disconnect();
};
}, []);
function resize(height) {
const iframe = window.frameElement;
iframe.height = height + 10;
}
初始化时增加监听,每次观测到目标dom大小变化时,重新设置iframe高度。由于监听只会在绘制前或布局后触发调用,所以不必考虑频繁触发,经测试,在一次渲染中,触发频率很低。