iframe
撰写时间:2025-11-15
修订时间:2025-12-14
iframe可在一个HTML网页另建一个独立的HTML环境。
当我们需要在网页中使用一个独立的HTML环境时,即可使用iframe达到目的。
srcdoc
srcdoc属性可用以便捷地设置iframe的内容。
下面的代码新建一个最简单的iframe,并查看其各个属性。
其contentDocument的DOM树形结构如下:
- hoster
- iframe
- #document
- html
- head
- body
- <h1>Hello, iframe.</h1>
- html
- #document
- iframe
即便是一个如此简单的iframe,它也以完整的HTML格式存储了相应的内容。
此外,iframe的contentDocument下面的HTML环境,与其宿主的环境HTML环境完全隔离。上面在宿主环境中将h1的颜色设置为红色,但在iframe中,其h1颜色不受此影响。
src
现有一个独立的HTML文件:
则可使用src属性来嵌入这个独立的HTML网页文件。
load事件
当我们设置img的src属性值时,需为其设置onload事件,以等待其加载完毕。iframe同样如此。
需注意的是,img在指定其src属性值后,无需将img添加到DOM树中,其load事件就能被触发;而与此不同,iframe需添加到DOM树后,其load事件才会被触发。
hello
`; iframe.onload = (evt) => { pc.log('iframe loaded'); }; pc.appendChild(iframe); // append into DOM tree to trigger onload eventcontentDocument
调用iframe的contentDocument,可在宿主环境中操控iframe的内容。
iframe的宽度与高度
与canvas一样,iframe的默认宽高值为300px × 150px。
宽度
iframe, html, body的width的内在依赖关系
当使用iframe的srcdoc时,其load事件须点击2次才会被触发。故完全改用JavaScript动态添加的方式。
尽管iframe的边框宽度为15px,因其box-sizing的值为content-box
,故并不影响其width属性值,仍为默认的300px。
默认情况下,html的margin及padding均为0px。
作为iframe的直接子元素,html的宽度将追随iframe的宽度,即,将在300px的范围内编排内容。由于我们为其设置了一条红边,且其计算盒子尺寸的方式为content-box
,即width及height的值均不包括边框厚度,故其width值应将父元素的宽度值300px减去左右两条边框线的厚度而为298px。
如果在CSS
面板中改变iframe宽度值,则看到html的宽度也随之改变。
至于body,其内容需在其父元素html的宽度298px内编排。但由于body的默认margin值为8px,故其width值为298 - 8 * 2 - 1 * 2 = 280px。也即,默认情况下,body的宽度值也受html的限制。
水平滚动时的溢出
作为最外层的容器,当子元素的宽度超出iframe的视口范围时,iframe会自动出现水平滚动条,以在水平方向上容纳所有子元素的内容。
A little long text that would exceed the width of 300px.
`; iframe.onload = (evt) => { pc.group('iframe'); pc.log('width: %s', getComputedStyle(iframe).width); pc.log('overflow: "%s"', getComputedStyle(iframe).overflow); pc.log('scrollWidth: %d', iframe.scrollWidth); pc.groupEnd(); pc.group('html'); let htmlEle = iframe.contentDocument.documentElement; pc.log('width: %s', getComputedStyle(htmlEle).width); pc.log('overflow: "%s"', getComputedStyle(htmlEle).overflow); pc.log('scrollWidth: %d', htmlEle.scrollWidth); pc.groupEnd(); pc.group('body'); let bodyEle = iframe.contentDocument.body; pc.log('width: %s', getComputedStyle(bodyEle).width); pc.log('magin: %s', getComputedStyle(bodyEle).margin); pc.log('overflow: "%s"', getComputedStyle(bodyEle).overflow); pc.log('scrollWidth: %d', bodyEle.scrollWidth); pc.groupEnd(); pc.group('p'); let pEle = iframe.contentDocument.querySelector('p'); pc.log('overflow: "%s"', getComputedStyle(pEle).overflow); pc.log('width: %s', getComputedStyle(pEle).width); pc.groupEnd(); }; pc.appendChild(iframe);当p的内容在水平方向上溢出时,上面在body上将overflow的值设为clip,则在body(灰色背景)这一层上,超出灰色背景的文本将被裁切。但由于html的overflow的值设为initial,其初始值即visible,故在html(黑色背景)这一层上仍能看到文本。
试将html的overflow的值也设为clip,则将只看到灰色背景的文本。
将html及body的overflow的值均设为initial,恢复它们的初始值visible,则出现滚动条以显示全部的文本内容。
A little long text that would exceed the width of 300px.
`; iframe.onload = (evt) => { pc.group('iframe'); pc.log('width: %s', getComputedStyle(iframe).width); pc.log('overflow: "%s"', getComputedStyle(iframe).overflow); pc.log('scrollWidth: %d', iframe.scrollWidth); pc.groupEnd(); pc.group('html'); let htmlEle = iframe.contentDocument.documentElement; pc.log('width: %s', getComputedStyle(htmlEle).width); pc.log('overflow: "%s"', getComputedStyle(htmlEle).overflow); pc.log('scrollWidth: %d', htmlEle.scrollWidth); pc.groupEnd(); pc.group('body'); let bodyEle = iframe.contentDocument.body; pc.log('width: %s', getComputedStyle(bodyEle).width); pc.log('magin: %s', getComputedStyle(bodyEle).margin); pc.log('overflow: "%s"', getComputedStyle(bodyEle).overflow); pc.log('scrollWidth: %d', bodyEle.scrollWidth); pc.groupEnd(); pc.group('p'); let pEle = iframe.contentDocument.querySelector('p'); pc.log('overflow: "%s"', getComputedStyle(pEle).overflow); pc.log('width: %s', getComputedStyle(pEle).width); pc.groupEnd(); }; pc.appendChild(iframe);可以看到,向右移动滚动条,可视区域紧挨着文本的结尾就结束了,也即仅限于content-box,不包含margin及padding。
对于iframe,Chrome的overflow的初始值为clip,Safari的overflow的初始值为visible。
clip意味着将可视区域限定在该元素的content-box内,而visible意味着超出content-box时仍可通过滚动条来查看。随着文本长度的增加,可视区域的宽度变大,但iframe的width值并不会改变。因此从这个意义上,个人认为,Safari在此点上的实现是精准的。
但实际上,即使在Safari中,将iframe的overflow的值设为clip,也不会发生裁切。可见规范对于出现滚动容器 (scroll container) 时如何应对的情形规定得还不是很明确。
scrollWidth
scrollWidth是指若在特定元素上设定滚动区域时该滚动区域的宽度。上面的滚动区域从红色的左边框开始,一直到黑色背景的右边。
由于body设有margin值,则body的scrollWidth值比html的值要小。小多少?body的margin加上html的border-width,则8 + 1 = 9。Chrome计算出的差值为9,正确;Safari计算出的差值为10,不准确。
在水平方向上最大化视口
上面的滚动容器是建立在哪个元素之上?由于滚动区域包含了所有的黑色背景区域,显然,滚动容器建立在iframe之上。但这又与iframe的scrollWidth属性值仅为300px相矛盾。
因此,如果我们希望在不出现滚动条能自动显示全部内容,则需将iframe的width值设置为html或body的scrollWidth的值。由于body总是位于html的内部,则应取最外面的容器html的scrollWidth值。
A little long text that would exceed the width of 300px.
`; iframe.onload = (evt) => { pc.group('iframe'); pc.log('width: %s', getComputedStyle(iframe).width); pc.log('overflow: "%s"', getComputedStyle(iframe).overflow); pc.log('scrollWidth: %d', iframe.scrollWidth); pc.groupEnd(); pc.group('html'); let htmlEle = iframe.contentDocument.documentElement; pc.log('width: %s', getComputedStyle(htmlEle).width); pc.log('overflow: "%s"', getComputedStyle(htmlEle).overflow); pc.log('scrollWidth: %d', htmlEle.scrollWidth); pc.groupEnd(); pc.group('body'); let bodyEle = iframe.contentDocument.body; pc.log('width: %s', getComputedStyle(bodyEle).width); pc.log('magin: %s', getComputedStyle(bodyEle).margin); pc.log('overflow: "%s"', getComputedStyle(bodyEle).overflow); pc.log('scrollWidth: %d', bodyEle.scrollWidth); pc.groupEnd(); pc.group('p'); let pEle = iframe.contentDocument.querySelector('p'); pc.log('overflow: "%s"', getComputedStyle(pEle).overflow); pc.log('width: %s', getComputedStyle(pEle).width); pc.groupEnd(); iframe.style.width = `${htmlEle.scrollWidth + parseFloat(getComputedStyle(bodyEle).margin)}px`; }; pc.appendChild(iframe);前面谈到,可视区域紧挨着文本的结尾就结束了,因此为保持左右边距的一致,还加上了body的右边距的值。
高度
html及body的height值会自动增大
A little long text that would exceed the width of 300px.
需注意的是,在上一节中当文本内容超出html及body的width值时,虽然出现了水平滚动条,但它们的width值不会改变。
与此不同的是,随着子元素的增加,html及body的height值会自动增加。
此外,scrollHeight是DOM元素的属性名,而不是CSS属性名。
最大化视口
观察垂直方向上的滚动区域,发现已自动包含上下两边的边距。因此iframe的height值只需取html的scrollHeight的值即可。
A little long text that would exceed the width of 300px.
在设置高度值时,因iframe有默认高度值150px,将影响到html的默认scrollHeight值。当html的高度值小于150px时,自动伸缩的高度值也将使用iframe的默认高度值150px,从而导致不能自动压缩高度。因此在设置iframe的高度值前,应先其值设置为0px。
CSS设置
在对iframe进行CSS设置时,需注意要分别进行两部分的设置。
上面代码将生成以下的DOM树:
- hoster stylesheets
- hoster container element
- iframe
- #document
- html
- head
- style
- body
- div
- head
- html
- #document
- iframe
上面srcdoc中的内容,对应于上图中#document
(也即iframe.contentDocument)中的内容,因此srcdoc所设置的CSS样式,只影响到#document
下面的各个元素。无法及于iframe自身。
如果我们要对iframe元素进行较为复杂的CSS设置(因此不是仅简单地修改iframe的style属性值),如取消其边框线条的inset设置,同时加上圆角边框,则需在上图中的hoster stylesheets
部分进行单独的设置。
pc.appendInlineStyle的作用,正在于在上图中的hoster stylesheets
部分插入一个内联样式单,从而可以改变iframe的CSS初始设置。
在iframe中玩转Color Scheme
在Web应用中,由于iframe可以提供一个完全独立于其宿主、且又具备相对完整的HTML环境,因此具有非常高的应用价值。它可以让我们在不破坏宿主环境的情况下,灵活、自由地初始化一个全新的HTML环境,从而可以制作、演化出众多丰富多彩的Web组件。
本节中,我们利用iframe的特性,逐步建立起一个功能完善而全面的、基于color scheme的小组件,从而洞悉颜色主题切换的所有细节。
让浏览器自动帮我们设置颜色
Hello, Color Scheme.
</body> `; initIframe(srcdoc); pc.appendHTMLStr('A paragraph outside iframe.
');上面第一步,对iframe进行CSS设置,使其尺寸铺满父元素的空间。
第二步,设置iframe的contentDocument的样式及实体内容。
通过设置color-scheme,且各个元素未设置任何与颜色相关的属性值,则浏览器根据操作系统中的颜色主题,自动帮我们设置各个元素的颜色。
在操作系统中切换颜色主题,可看到iframe中的p元素的color及background-color值也自动随之变化。
可以看到,iframe的CSS设置只及于第一个p元素,不影响第二个p元素。这里插入第二个p元素的意义,仅在于确认iframe的CSS影响范围,下面的各小节将删除它。
设置自定义颜色主题
Hello, Color Scheme.
</body> `; initIframe(srcdoc);使用CSS的light-dark函数,为body及其子元素分别设置高亮模式及暗黑模式下的color及background-color属性值。自此,对这两个属性,浏览器不再帮我们自动设置其值。
获取并显示系统颜色主题
Hello, Color Scheme.
</body> `; initIframe(pc, srcdoc); function initIframe(pc, srcdoc) { pc.appendInlineStyle(` ul.pageconsole-output { > li:has(iframe) { padding-left: 0em; display: flex; align-items: center; > iframe { border-width: 0px; width: 100%; } } } `); let iframe = document.createElement('iframe'); iframe.srcdoc = srcdoc; iframe.onload = (evt) => { let doc = iframe.contentDocument; autoHeight(iframe, doc); showSysInfo(doc); }; pc.appendChild(iframe); } function autoHeight(iframe, doc) { iframe.style.height = '0px'; iframe.style.height = `${doc.documentElement.scrollHeight}px`; } function showSysInfo(doc) { showSysColorScheme(doc); matchMedia('(prefers-color-scheme: light)').onchange = (evt) => { showSysColorScheme(doc); }; } function showSysColorScheme(doc) { let checkedEle = doc.querySelector('[name=system-color-scheme][checked]'); checkedEle?.removeAttribute('checked'); let sysColorScheme = getSystemColorScheme(); doc.querySelector(`#sys-${sysColorScheme}`).toggleAttribute('checked'); } function getSystemColorScheme() { if (matchMedia('(prefers-color-scheme: light)').matches) { return 'light'; } else if (matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } else { return 'unset'; } }在getSystemColorScheme函数中,调用matchMedia方法来读取操作系统中颜色主题的设置值。一些操作系统未提供颜色主题的切换功能,则返回unset
。
在showSysInfo函数中,先调用showSysColorScheme函数将读取到的值显示在界面中;而如果操作系统支持颜色主题切换,则在操作系统的颜色主题切换时,再调用一次showSysColorScheme以反映最新的操作系统设置值。
在操作系统中切换颜色主题,左边方框可自动反映出该主题值,并且网页中的上述输出区域自动随之变换颜色主题。
允许用户自主选择
允许用户自主选择的核心在于,如果将color-scheme的值设为light dark,则可响应操作系统中颜色主题变换的通知并使用相应的颜色主题;如果将color-scheme的值仅设为light或dark,则浏览器将忽略操作系统发来的通知,直接使用所指定的颜色主题。
Hello, Color Scheme.
</body> `; initIframe(pc, srcdoc); function initIframe(pc, srcdoc) { pc.appendInlineStyle(` ul.pageconsole-output { > li:has(iframe) { padding-left: 0em; display: flex; align-items: center; > iframe { border-width: 0px; width: 100%; } } } `); let iframe = document.createElement('iframe'); iframe.srcdoc = srcdoc; iframe.onload = (evt) => { let doc = iframe.contentDocument; autoHeight(iframe, doc); showSysInfo(doc); handleUserSelect(doc); }; pc.appendChild(iframe); } function autoHeight(iframe, doc) { iframe.style.height = '0px'; iframe.style.height = `${doc.documentElement.scrollHeight}px`; } function showSysInfo(doc) { showSysColorScheme(doc); matchMedia('(prefers-color-scheme: light)').onchange = (evt) => { showSysColorScheme(doc); }; } function showSysColorScheme(doc) { let checkedEle = doc.querySelector('[name=system-color-scheme][checked]'); checkedEle?.removeAttribute('checked'); let sysColorScheme = getSystemColorScheme(); doc.querySelector(`#sys-${sysColorScheme}`).toggleAttribute('checked'); } function getSystemColorScheme() { if (matchMedia('(prefers-color-scheme: light)').matches) { return 'light'; } else if (matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } else { return 'unset'; } } function handleUserSelect(doc) { let userRadios = doc.querySelectorAll('[name=user-color-scheme]'); userRadios.forEach(ele => { ele.onclick = (evt) => { let value = ele.value; if (value === 'system') { doc.documentElement.style.colorScheme = 'light dark'; } else { doc.documentElement.style.colorScheme = value; } }; }); }在handleUserSelect函数中,为3个radios设置点击响应事件,根据它们的值相应地设置color-scheme的值。
如果点击system
,则网页中的输出区域可随操作系统中的颜色主题变化而变化;如果点击light
或dark
,则无论操作系统的设置为何值,输出区域立即根据用户选择调整相应的颜色主题。
使用本地存储保存用户的选择
如果希望记住用户的偏好,则可将用户的当前选择保存起来,然后再根据用户的偏好设置color-scheme的值。
作为示范,本例使用sessionStorage来保存。原因有二:
- 本站已经使用localStorage来保存用户偏好。因此例子不会影响本站应用的功能。
- 保存在sessionStorage的值只在浏览器当前标签页中有效。打开新的标签页,则需重新保存。这样方便调试各种状态。
若用在正式发布的应用中,只需将sessionStorage改为localStorage即可。
要实现此功能,需按3步骤进行。
第一步,先读取用户偏好,并根据其值设置color-scheme的值。
在用户第一次加载网页时,sessionStorage尚未保存任何值,则返回system
,根据操作系统的设置来决定颜色主题。
在初始化srcdoc时,如果变量userPreference的值为system
,则将color-scheme的值设为light dark;否则直接取其值即可。
第二步,将用户偏好显示在界面中。
第三步,修改handleUserSelect函数。
当用户在界面中选择偏好时,先设置相应的color-scheme的值,同时将用户偏好值保存进sessionStorage中。
当这一步完成之后,重新刷新网页,则上述第一步的代码即可取出所保存的用户偏好值,用以动态设置color-scheme的值。
下面是可运行的完整代码。
Hello, Color Scheme.
</body> `; initIframe(pc, srcdoc); function initIframe(pc, srcdoc) { pc.appendInlineStyle(` ul.pageconsole-output { > li:has(iframe) { padding-left: 0em; display: flex; align-items: center; > iframe { border-width: 0px; width: 100%; } } } `); let iframe = document.createElement('iframe'); iframe.srcdoc = srcdoc; iframe.onload = (evt) => { let doc = iframe.contentDocument; autoHeight(iframe, doc); showSysInfo(doc); showUserPreference(doc); handleUserSelect(doc); }; pc.appendChild(iframe); } function autoHeight(iframe, doc) { iframe.style.height = '0px'; iframe.style.height = `${doc.documentElement.scrollHeight}px`; } function showSysInfo(doc) { showSysColorScheme(doc); matchMedia('(prefers-color-scheme: light)').onchange = (evt) => { showSysColorScheme(doc); }; } function showUserPreference(doc) { let ele = doc.querySelector(`[name=user-color-scheme][value=${userPreference}]`); ele.toggleAttribute('checked'); } function showSysColorScheme(doc) { let checkedEle = doc.querySelector('[name=system-color-scheme][checked]'); checkedEle?.removeAttribute('checked'); let sysColorScheme = getSystemColorScheme(); doc.querySelector(`#sys-${sysColorScheme}`).toggleAttribute('checked'); } function getSystemColorScheme() { if (matchMedia('(prefers-color-scheme: light)').matches) { return 'light'; } else if (matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } else { return 'unset'; } } function handleUserSelect(doc) { let userRadios = doc.querySelectorAll('[name=user-color-scheme]'); userRadios.forEach(ele => { ele.onclick = (evt) => { let value = ele.value; if (value === 'system') { doc.documentElement.style.colorScheme = 'light dark'; } else { doc.documentElement.style.colorScheme = value; } sessionStorage.setItem('color-theme', value); }; }); } function getUserPreference() { let userPrevColorTheme = sessionStorage.getItem('color-theme'); if (!userPrevColorTheme) { return 'system'; } else { return userPrevColorTheme; } }应用运行的效果是,若用户选择system
,则输出区域的颜色主题跟随操作系统的颜色主题的设置而改变。在操作系统中切换颜色主题,输出区域自动随之变亮或变暗。
点击light
或dark
,再刷新网页,将会直观地看到,在本例中浏览器记住了我们的选择,不管操作系统或是网页的整体颜色主题是什么,本例的输出区域均仅会根据用户选择进行输出。
