Iframes that finally fit their content
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
Iframes that finally fit their content
Chrome 154 lets an iframe grow to the height of its content with one line of CSS without having to measure, without messages, and without resize scripts. But the page inside the iframe has to agree to it. That catch is the most interesting part of this feature, so this article spends some time on it.
Chrome 154 允许 iframe 通过一行 CSS 代码自动调整高度以适应其内容,无需测量、无需消息传递,也无需调整大小的脚本。但 iframe 内部的页面必须同意此操作。这一限制是该功能最有趣的部分,因此本文将对此进行详细探讨。
The problem I faced I have built many checkout pages that use a payment provider’s iframe. The goal was always to have the card form feel like part of the page. The user should not notice that it comes from another website. The iframe never helped with that. It has a fixed height. Make it too tall and you get an empty gap under the form. Make it too short and you get a scrollbar inside the page’s scrollbar. On mobile, that second scrollbar is the worst thing you can show someone who is about to pay. So I kept increasing the height, testing on different phones, and increasing it again. This is a small problem. It should have a small solution. For years it did not.
我曾面临的问题是,我构建过许多使用支付服务商 iframe 的结账页面。目标始终是让信用卡表单看起来像是页面的一部分,用户不应察觉它来自其他网站。然而,iframe 从未提供过帮助,因为它具有固定的高度。设置得太高,表单下方会出现空白;设置得太短,页面滚动条内会出现另一个滚动条。在移动设备上,这种双重滚动条对于即将付款的用户来说体验极差。因此,我不得不不断增加高度,在不同手机上测试,然后再增加。这是一个小问题,本应有一个简单的解决方案,但多年来一直没有。
How we used to do it The parent page cannot look inside a cross-origin iframe. It does not know how tall the content is. So the only way was to make both pages talk to each other with JavaScript. Inside the iframe, you measure the content and send the height to the parent: const send = () => { const height = document.documentElement.scrollHeight; parent.postMessage({ type: ‘resize’, height }, ‘https://shop.example’); }; new ResizeObserver(send).observe(document.body); In the parent page, you listen, check who sent the message, and set the height: window.addEventListener(‘message’, (event) => { if (event.origin !== ‘https://pay.example’) return; if (event.data?.type !== ‘resize’) return; document.querySelector(‘#payment-frame’).style.height = ${event.data.height}px; }); This looks short, but it hides a lot of problems: Width changes height. On a small screen, text wraps more and the content gets taller. Every resize or phone rotation needs a new measurement. Content arrives late. Fonts, images and error messages load after the first measurement. The height is wrong until someone measures again. Dynamic content. If the page inside the iframe shows or hides elements after the initial load, the height changes and needs to be measured again. More than one iframe. If the page has several, you need IDs in every message to know which one to resize. Both sides must cooperate. If the third party does not send its height, there is nothing you can do. You go back to guessing a fixed height. That last point did not go away with the new feature. It just moved into the browser.
过去的做法是,父页面无法查看跨域 iframe 内部的内容,因此不知道内容的高度。唯一的方法是让两个页面通过 JavaScript 进行通信。在 iframe 内部,测量内容并将高度发送给父页面:const send = () => { const height = document.documentElement.scrollHeight; parent.postMessage({ type: 'resize', height }, 'https://shop.example'); }; new ResizeObserver(send).observe(document.body); 在父页面中,监听消息、验证发送者并设置高度:window.addEventListener('message', (event) => { if (event.origin !== 'https://pay.example') return; if (event.data?.type !== 'resize') return; document.querySelector('#payment-frame').style.height = ${event.data.height}px; }); 这看起来很简单,但隐藏了许多问题:宽度会改变高度(小屏幕上文字换行更多,内容变高);每次调整大小或旋转手机都需要重新测量;内容加载延迟(字体、图片和错误消息在首次测量后才加载,导致高度错误);动态内容(iframe 内页面显示或隐藏元素时,高度会改变);多个 iframe(需要 ID 来区分);双方必须配合(如果第三方不发送高度,你只能回到猜测固定高度的老路)。最后一点并没有随着新功能消失,只是转移到了浏览器层面。
The new way In the parent page, you add one CSS property to the iframe: .embed { width: 100%; frame-sizing: content-height; } Inside the iframe, the page opts in with a meta tag in its . It also says which websites are allowed to size it: That is all you need for content that does not change after it loads. The browser measures the content and sizes the iframe. No messages, no listeners, no origin checks in your code. The meta tag must be in the HTML from the start. Adding it later with JavaScript does not work. If the content changes later (an error message appears, a section expands, more comments load), the page inside the iframe asks the browser to measure again: window.requestResize(); So JavaScript does not disappear completely. The embedded page still calls one function when its content changes. But the hard parts are gone. The browser does all of that now. frame-sizing also accepts content-width, content-inline-size and content-block-size. For most pages, content-height is the one you want. You can still combine it with limits like max-height: 80vh.
新的方式是,在父页面中为 iframe 添加一个 CSS 属性:.embed { width: 100%; frame-sizing: content-height; }。在 iframe 内部,页面通过 <head> 中的 meta 标签选择加入,并指定允许哪些网站调整其大小:<meta name="responsive-embedded-sizing" content="allow-origins=https://shop.example">。对于加载后内容不变的情况,这就足够了。浏览器会自动测量内容并调整 iframe 大小,无需代码中的消息、监听器或来源检查。meta 标签必须从一开始就存在于 HTML 中,后期通过 JavaScript 添加无效。如果内容后续发生变化(如出现错误消息、部分展开或加载更多评论),iframe 内部页面需请求浏览器重新测量:window.requestResize();。因此,JavaScript 并未完全消失,嵌入页面在内容变化时仍需调用此函数,但繁琐的部分已由浏览器处理。frame-sizing 还支持 content-width、content-inline-size 和 content-block-size,但对于大多数页面,content-height 是最需要的,且可以与 max-height: 80vh 等限制结合使用。
Where this helps Payment forms This is the use case I care about most, and it is also the one that depends most on someone else. A payment form changes height all the time. An error appears under the card number. The user switches from card to wallet. A saved card list shows up. With frame-sizing, all of this could look smooth inside your checkout. But you cannot turn it on from your side alone. You control the CSS on your checkout page. The payment provider controls the page inside the iframe. Only they can add the meta tag, list the merchant sites that are allowed, and call requestResize() when their form changes. So for payments, the real question is not “does Chrome support this?” It is “does my payment provider support this?” Today, most providers either ship their own postMessage script or do nothing. If you work with one, ask them. If you build one, this is a cheap win for every merchant using you.
该功能适用的场景包括:支付表单(这是我最关心的场景,也最依赖第三方)。支付表单高度经常变化,例如卡号下方出现错误、用户切换支付方式或显示已保存的卡列表。使用 frame-sizing 可以让这些变化在结账页面中平滑过渡。但你无法单方面开启此功能,因为你只控制结账页面的 CSS,而支付服务商控制 iframe 内部页面。只有他们能添加 meta 标签、列出允许的商户站点并在表单变化时调用 requestResize()。因此,对于支付而言,真正的问题不是“Chrome 是否支持”,而是“我的支付服务商是否支持”。目前,大多数服务商要么使用自己的 postMessage 脚本,要么什么都不做。如果你与他们合作,请询问他们;如果你是服务商,这对所有使用你的商户来说都是一个低成本的优化。
Third-party widgets Comment sections, contact forms, newsletter sign-ups and booking widgets all have the same problem. Their height depends on the content: the number of comments, the number of fields, the validation errors. These services already control their embedded page, so adding the meta tag is easy for them. The allow-origins list fits well here, because they already know which customer sites embed them. I have this exact problem on this website. My contact form is a Wufoo form inside an iframe, and I set its height by hand until it looked right.
第三方小部件(如评论区、联系表单、新闻通讯注册和预订小部件)都有同样的问题,其高度取决于内容(评论数量、字段数、验证错误)。这些服务商已经控制了嵌入页面,因此添加 meta 标签对他们来说很容易。allow-origins 列表在这里非常适用,因为他们已经知道哪些客户网站嵌入了他们。我的网站就存在这个问题,我的联系表单是一个嵌入在 iframe 中的 Wufoo 表单,我不得不手动设置其高度以使其看起来正常。
Multi-step forms and surveys Each step of a form has a different height. Step one has two fields. Step three has ten. Today, you either reserve space for the tallest step or let the iframe scroll. With this feature, the form calls requestResize() after each step and the iframe follows.
多步骤表单和调查(表单的每个步骤高度不同,例如第一步有两个字段,第三步有十个)。目前,你必须为最长的步骤预留空间,或者让 iframe 出现滚动条。有了此功能,表单在每一步之后调用 requestResize(),iframe 即可自动跟随调整。
Content you render yourself This one does not need any third party. Many apps show HTML email previews, rich text previews, or code demos inside a sandboxed iframe using srcdoc. Here you write the embedded HTML yourself, so you can add the meta tag directly. This is probably the easiest place to start using the feature today.
自行渲染的内容(这不需要任何第三方)。许多应用使用 srcdoc 在沙盒 iframe 中显示 HTML 邮件预览、富文本预览或代码演示。由于嵌入的 HTML 是你自己编写的,你可以直接添加 meta 标签。这可能是目前开始使用该功能最简单的地方。
Before you ship Browser support. This is Chromium-only for now. Firefox and Safari do not support it yet. Keep a fixed height as the default and switch to content sizing on
在发布之前,请注意浏览器支持情况。目前该功能仅支持 Chromium 内核,Firefox 和 Safari 尚不支持。请保持固定高度作为默认设置,并在…上切换到内容自适应大小。