在中后台管理系统中,由于业务功能模块的复杂性,使用 iframe 是不可避免的。当需要嵌入单独实现的或由另外一个系统提供的页面功能模块时,你只能选择 iframe。
本文尝试对 iframe 应用实践中常见的问题进行简要整理。
1 HTML <iframe> 标签
iframe 用于创建页内框架,以在当前页面中嵌入另外一个页面。所有浏览器都支持 <iframe>
标签。
iframe 属性
可设置的属性主要有 iframe 特有属性、HTML 全局属性和事件属性。这里不作详述,具体请参考:
- http://www.w3school.com.cn/tags/html_ref_standardattributes.asp
- https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe
2 iframe 常用场景
- 嵌入并展示另外一个页面的功能模块
- 非 formData 方式的兼容低版本浏览器的异步上传实现
- more…
3 背景透明的 iframe
如主页面设置了背景色或背景图片,iframe 区域便会有一个白色区块,这是因为 iframe 默认背景为白色。这时可能你需要让 iframe 背景透明。方法为:
设置 allowtransparency="true"
属性,并设置样式: background-color: transparent
。
示例代码:
<iframe id="lzwmeIframe" src="https://lzw.me" allowtransparency="true" style="background-color: transparent"> </iframe>
4 与 iframe 通信
4.1 iframe 同域名通信
当 iframe 中的页面域名与主页面域名相同时,可以直接通过互相访问文档内容来通信。
const iframe = document.getElementById('lzwmeIframe'); const height = iframe.contentWindow.document.body.scrollHeight; console.log('子页面高度为:', height);
4.2 iframe 跨域通信
当 iframe 中的页面与主页面域名不同,则因安全问题不可互相访问具体文档。此时需通过其他方式进行。
4.2.1 设置 document.domain
方式
当 iframe 中的页面与主页面的顶级域名相同时,可以通过在两个页面中都设置 document.domain
为顶级域名,如此双方即可互相访问文档内容,则与同域名方式相同。示例:
document.domain = 'lzw.me';
4.2.2 postMessage 方式
通过 postMessage 是当前最简单的实现方式。具体参见下面的 跨域下的 iframe 高度自适应示例代码。
关于 postMessage 的用法可参考:
- https://lzw.me/a/html5-postmessage-post-cross-domain.html
- https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage
4.2.3 通过后端 API 中转
通过与后端 API 结合,在子窗体向服务器发送自身相关数据,在父窗体向后端 API 请求该数据,由此通过 API 中转实现通信,也是一种兼容各种情况下的常见实现思路。
此方法对前端来说稍微复杂,如非必要不建议选择此方案。
5 iframe 高度自适应
让 iframe 高度与内部页面文档高度相同,即可以让 iframe 没有滚动条。
iframe 高度自适应主要通过主页面与 iframe 通信的方式来实现。
5.1 同域名下的 iframe 高度自适应示例
同域名下访问 iframe 内的文档窗体高度来设置 iframe 高度即可。示例:
const iframe = document.getElementById('lzwmeIframe'); function setIframeHeight() { try { const docEl = iframe.contentWindow.document.documentElement; iframe.style.height = Math.max(docEl.offsetHeight, docEl.scrollHeight) + 'px'; iframe.style.width = Math.max(docEl.offsetWidth, docEl.scrollWidth) + 'px'; console.log(iframe.style.height, iframe.style.width); } catch(e) { console.log('读取 iframe 内文档异常:', e); } } // iframe 加载时 iframe.addEventListener('load', setIframeHeight); // 窗体大小改变时 window.addEventListener('resize', setIframeHeight); iframe.src = 'https://lzw.me';
5.2 跨域下通过 postMessage 实现 iframe 高度自适应示例
跨域下可通过 postMessage 通信实现。
在父页面中监听 message
事件,得到子窗体高度时改变 iframe 高度:
const iframe = document.getElementById('lzwmeIframe'); addEventListener('message', function(ev) { const data = ev.data; if (data.height) { iframe.style.height = data.height + 'px'; } }); // 窗体大小改变,请求子页面再次发送 addEventListener('resize', function() { iframe.contentWindow.postMessage({action: 'getHeight'}, '*'); });
在子页面中,当页面加载完成时,以及监听到 message
事件得到获取高度请求时,发送自己的文档高度:
let parent; if (window.parent !== window) { parent = window.parent; } function sendHeight() { if (!parent) return; const docEl = document.documentElement; const height = Math.max(docEl.offsetHeight, docEl.scrollHeight); parent.postMessage({ height: height }, '*'); } addEventListener('load', function() { sendHeight(); }); addEventListener('message', function(ev) { if (ev.data.type !== 'getHeight') { return; } if (!parent) { parent = ev.source; } sendHeight(); });
6 iframe 权限与安全
6.1 https 页面内的 iframe
如页面网址为 https 方式,则浏览器因安全问题,会阻塞(block) 对 http 资源的请求。
那么 iframe 中也是一样的,即主页面使用了 https,则 iframe 中内嵌的页面也必须为 https 方式。
对于此问题,有如下几种解决方案:
- 如 iframe 中的站点是可控的,则配置它支持 https 即可。
- 将要 iframe 的 http 的站点进行代理转发,转发到同域下的 https 的域名下。但这种方案会存在较多的细节问题需针对性处理,一般很难实现完全兼容。
- 配置让主页面站点同时支持 http 和 https,需要在 iframe 中加载 http 方式的地址时,主页面强制跳转到 http 域名上。
6.2 sandbox 沙盒属性
通过设置 sandbox
属性,可以启用一系列对 iframe 中内容的额外限制。其取值可为以下几种的组合:
""
为空,即不设置,默认不作任何限制。allow-forms
允许表单提交allow-same-origin
允许 iframe 内容被视为与包含文档有相同的来源allow-scripts
允许脚本执行allow-top-navigation
允许 iframe 内容从包含文档导航(加载)内容(即允许从 iframe 内修改父级的页面跳转)
当不为空时,不包含在取值列表中的项即被禁用。
例如,iframe 中的页面行为可能是不可控的,为了防止其将父页面跳转,可以限制 allow-top-navigation
,不允许从 iframe 中修改其父页面导航。示例代码:
<iframe sandbox="allow-scripts allow-same-origin allow-forms" name="lzwme" class="lzwme-iframe" id="lzwmeIframe" frameborder="0" scrolling="no" width="360" height="640" allowtransparency="true" frameborder="0" src=""></iframe>
如此设置后 iframe 内的(跨域名的)页面,即无法强制父窗体跳转。但同源地址仍然可有权限跳转,如有需要可作进一步限制,即移除 allow-same-origin
。但有一点需注意,移除 allow-same-origin
后,iframe 内想读取顶级域名 cookie 等行为也会被禁止,这可能会带来一些问题,另外跨域的字体加载也会失败,所以原则上不建议移除它。
7 iframe 其他问题
7.1 自适应高度后的窗体高度问题
当对 iframe 实现自适应高度后,通过 window.innerHeight
读取的值与其内部 body 的值一致。当内部页面存在屏幕居中的 modal 浮层弹窗时,则会出现问题:因为它一般是通过 window.innerHeight
来得到窗体高度,然后计算居中位置的,那么弹窗很可能就出现在实际上看不到的地方。
针对这种情况,我们的建议是不要做高度自适应,改用 calc
动态计算 iframe 高度,让 iframe 存在滚动条,页面不存在即可。示例:
addEventListener('resize', function() { const iframe = document.getElementById('lzwmeIframe'); const top = iframe.getBoundingClientRect().top; const height = `calc(100vh - ${top}px)`; if (height !== iframe.style.height) { iframe.style.height = height; } }); addEventListener('load', function() { window.resize(); });
8 相关参考
- http://www.w3school.com.cn/tags/html_ref_standardattributes.asp
- https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe
- https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage