Web 平台 API / iframe 的尺寸 · 让内容决定高度 / frame-sizing:双方同意才按内容定高 待审核 1 / 4
frame-sizing · meta

frame-sizing:双方同意才按内容定高

<iframe> 是一个替换元素。它和 <img> 一样有一个天然尺寸,只是这个尺寸与内容无关:规范给的默认值是 300 × 150。内嵌文档写得再长,iframe 也只有 150px 高,多出的部分变成 iframe 内部的滚动条。

1 · 为什么默认不跟内容走

一个 <div> 的高度由内容撑开,iframe 不这样做有它的理由。iframe 的内容尺寸本身就是信息:一个恶意页面嵌入某银行的「我的账户」页,若 iframe 的高度随内容变化,外层读一下 getBoundingClientRect() 就能分辨出用户是否已登录、账户列表有几行。固定尺寸让跨源 iframe 的内容对外层完全不可观察。

同源场景没有这个顾虑,但规则对所有 iframe 一视同仁。于是评论组件、嵌入式代码演示、第三方表单这类需要「多高就多高」的用法,只能靠脚本把尺寸传出来。HTML5 草案曾有一个 seamless 属性承诺这件事,它只在个别浏览器的实验开关后出现过,后来从规范中移除。2016 年 1 月 whatwg/html#555 重新提出「让 iframe 自动调整尺寸」,2017 年 8 月 Tab Atkins 在 CSSWG 开了 #1771。规范文本到 2026 年才写进 CSS Sizing 4,Chrome 154(2026-09-22)是第一个实现(兼容性据 MDN BCD,Firefox 与 Safari 均未实现,核对于 2026-09)。

最终的设计是双向同意:外层声明它想按内容定尺寸,内嵌文档声明它愿意公开尺寸,缺一不可。外层单方面的声明无法读到任何东西,隐私边界因此保持不变。

2 · 外层的声明

iframe {
  frame-sizing: content-height;
  height: auto;
}

frame-sizing 的取值是 auto(初始值,忽略内容尺寸)、content-width、content-height、content-block-size 与 content-inline-size。规范把内嵌文档报来的尺寸称为 internal layout intrinsic size,content-height 让 iframe 的天然高度取它的高度一项,宽度照常计算。逻辑方向按 iframe 自己的 writing mode 解析,与内嵌文档无关。

它改的是天然尺寸,与 <img> 的天然尺寸处在同一层级,所以 CSS 的显式尺寸仍然优先。实测外层写 height: 100px 时,内嵌文档已经同意,iframe 仍是 100px;写 height: auto; max-height: 200px 时,420px 的内容被夹到 200px。按内容定高的前提是高度没有被别的声明定死。

3 · 内嵌文档的 meta

内嵌文档的同意方式是一条 <meta>。规范给每个文档一个 responsive embedded sizing flag,在首次解析时定下:解析器先遇到这条 <meta>,flag 为真;先打开 <body>(显式写出或被隐式打开),flag 为假。分界是 <body> 打开的时刻,<meta> 由谁写出不影响判定:WPT 的 responsive-iframe-meta-* 一组用例里,<head> 中的脚本在 <body> 打开前 append 一条 <meta>、把已有 <meta> 的 name 改成 responsive-embedded-sizing、用 document.write() 写出,都判为同意;同样的操作放到 <body> 打开之后则不生效。flag 定下后在整个文档生命周期内不再改变,事后删掉这条 <meta> 也不会撤回同意。wpt.fyi 上 Edge 154 这组用例全部通过(核对于 2026-09)。

规范的例子只写了 name:

<meta name="responsive-embedded-sizing">

实测 Chrome 154 不认这种写法:只写 name 的内嵌文档,iframe 停在 150px,文档内调用 requestResize() 抛 NotAllowedError,与没写 <meta> 完全相同。能生效的写法都带一个 content:(核对于 2026-09)

<meta name="responsive-embedded-sizing" content="allow-origins=https://blog.example">

allow-origins 列出允许读取尺寸的外层 origin。实测写外层自己的 origin 或 * 都生效;写成别的站(https://example.com)时同样停在 150px 并抛 NotAllowedError,协议写错(外层是 http、值写 https)也一样。CSS Sizing 4 的正文与 explainer 都没有定义 allow-origins 的语法,explainer 的示例用的是 allow-origins=*,只在隐私一节提到「可以进一步限制为明确允许的 origin」。登记这条 <meta> 的 HTML PR(whatwg/html#12444)在 2026 年 5 月提出,至今未合入(核对于 2026-09)。在规范补齐之前,allow-origins 的精确语法以 Chrome 的实现为准,而这套实现已经写成了测试:WPT 的 responsive-iframe-allow-origins.html 用 25 个 variant 逐项断言 iframe 高度是 400px(生效)还是 150px(不生效),它也是 Interop 2027 提案 interop#1443 引用的测试集。按写法归纳如下(核对于 2026-09):

content 的写法 WPT 期望
allow-origins=*,或写明外层 origin 生效,同源、跨站、子域嵌入都一样
只列别的 origin 不生效
多个 origin 以空格分隔,其中一个是外层 生效
只写 scheme,如 allow-origins=http: 外层 scheme 相同即生效,写成 https: 不生效
子域通配,如 allow-origins=http://*.example.test:8000 只匹配子域:外层在子域上时生效,外层就是 example.test 本身时不生效
'self',或写明内嵌文档自己的 origin 只在同源嵌入时生效,跨站嵌入时不生效
'none'、allow-origins=、allow-origins= 后只有空格 不生效
缺前缀(*)、前缀拼错(alloworigins=*)、前缀大写(ALLOW-ORIGINS=*) 不生效,前缀区分大小写
不写 content 不生效

最后一行就是上文只写 name 的情形。WPT 同样期望 150px,可见 Chrome 154 的表现是有意为之,与之不符的是规范示例。图 3-1 的 <meta> 写法只落在表中三行(* 与本站 origin、别的站、不写 content),其余六行没有进 lab。wpt.fyi 上 Edge 154 这 25 项全部通过;Firefox Nightly 尚未实现,期望 400px 的各项都停在 150px(核对于 2026-09)。

图 3-1 · 外层三种高度声明与内嵌文档六种 <meta> 写法的组合。可切换两侧写法与内容高度,观察 iframe 高度轨迹与 requestResize() 的结果。

警示 · 本页的 lab 只能演示同源:内嵌文档由 srcdoc 生成,与外层同源。作者另用两个本地 origin(localhost 嵌入 127.0.0.1,Chrome 会把它们放进不同进程)测过跨站,headless Chrome 154 里 iframe 高度多次读到 0,只有第一次测量读到过正确的 500px,无法稳定复现。跨站场景的结论本页不下,部署前应在目标浏览器里实测(核对于 2026-09)。

4 · 尺寸何时计算

内嵌文档的尺寸不是实时同步的。规范规定两个时刻:DOMContentLoaded 之后的第一次布局,以及 load 事件触发时。图 3-1 的轨迹从 iframe 插入即开始采样,读到的是 150 → 420:加载完成前是默认的 150px,之后一次跳到内容高度。

这个「跳一次」意味着按内容定高的 iframe 必然有一次布局位移。位于首屏的 iframe,这次位移读者看得见;位于屏幕下方的 iframe 若同时懒加载,位移发生在视口之外,见 懒加载 §3。加载之后内容再变化,iframe 不会跟着变,需要内嵌文档主动调用 requestResize(),下一页展开。