html 标签 iframe 基础与应用实践

目录
[隐藏]

在中后台管理系统中,由于业务功能模块的复杂性,使用 iframe 是不可避免的。当需要嵌入单独实现的或由另外一个系统提供的页面功能模块时,你只能选择 iframe。

本文尝试对 iframe 应用实践中常见的问题进行简要整理。

1 HTML <iframe> 标签

iframe 用于创建页内框架,以在当前页面中嵌入另外一个页面。所有浏览器都支持 &lt;iframe&gt; 标签。

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
点赞 (6)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code