Web编程技术营地
研究、演示、创新

iframe

撰写时间:2025-11-15

修订时间:2025-12-14

iframe可在一个HTML网页另建一个独立的HTML环境。

当我们需要在网页中使用一个独立的HTML环境时,即可使用iframe达到目的。

srcdoc

srcdoc属性可用以便捷地设置iframe的内容。

下面的代码新建一个最简单的iframe,并查看其各个属性。

let iframe = document.querySelector('iframe'); pc.log('%O', iframe); pc.log('%s', iframe.contentDocument.constructor.name);
body { display: flex; } h1 { color: red; } iframe { border: 10px solid gray; }

contentDocumentDOM树形结构如下:

  • hoster
    • iframe
      • #document
        • html
          • head
          • body
            • <h1>Hello, iframe.</h1>

即便是一个如此简单的iframe,它也以完整的HTML格式存储了相应的内容。

此外,iframecontentDocument下面的HTML环境,与其宿主的环境HTML环境完全隔离。上面在宿主环境中将h1的颜色设置为红色,但在iframe中,其h1颜色不受此影响。

src

现有一个独立的HTML文件:

<!DOCTYPE html> <html> <head> <title>A Simple HTML Page</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="/css/color-set.css" /> <link rel="stylesheet" href="/css/colors-def.css" /> <style> body { color: var(--dark-body-color); background-color: var(--dark-body-bg-color); } </style> </head> <body> <h1>A Simple HTML Page</h1> </body> </html>

则可使用src属性来嵌入这个独立的HTML网页文件。

load事件

当我们设置imgsrc属性值时,需为其设置onload事件,以等待其加载完毕。iframe同样如此。

let iframe = document.querySelector('#demo-iframe'); iframe.onload = (evt) => { pc.log('iframe is loaded.'); };
body { display: flex; }

需注意的是,img在指定其src属性值后,无需将img添加到DOM树中,其load事件就能被触发;而与此不同,iframe需添加到DOM树后,其load事件才会被触发。

let iframe = document.createElement('iframe'); iframe.srcdoc = `

hello

`; iframe.onload = (evt) => { pc.log('iframe loaded'); }; pc.appendChild(iframe); // append into DOM tree to trigger onload event

contentDocument

调用iframecontentDocument,可在宿主环境中操控iframe的内容。

let iframe = document.querySelector('#demo-iframe'); iframe.onload = (evt) => { let doc = iframe.contentDocument; pc.log('%O', doc); let h1 = doc.querySelector('h1'); h1.style.color = 'cadetblue'; };
body { display: flex; }

iframe的宽度与高度

canvas一样,iframe的默认宽高值为300px × 150px

宽度

iframe, html, body的width的内在依赖关系

let iframe = document.createElement('iframe'); iframe.srcdoc = ` `; iframe.onload = (evt) => { pc.group('iframe'); pc.log('width: %s', getComputedStyle(iframe).width); pc.log('height: %s', getComputedStyle(iframe).height); pc.log('box-sizing: "%s"', getComputedStyle(iframe).boxSizing); pc.groupEnd(); pc.group('html'); let htmlEle = iframe.contentDocument.documentElement; pc.log('width: %s', getComputedStyle(htmlEle).width); pc.log('height: %s', getComputedStyle(htmlEle).height); pc.log('box-sizing: "%s"', getComputedStyle(htmlEle).boxSizing); pc.log('magin: %s', getComputedStyle(htmlEle).margin); pc.log('padding: %s', getComputedStyle(htmlEle).padding); pc.log('border-width: %s', getComputedStyle(htmlEle).borderWidth); pc.groupEnd(); pc.group('body'); let bodyEle = iframe.contentDocument.body; pc.log('width: %s', getComputedStyle(bodyEle).width); pc.log('height: %s', getComputedStyle(bodyEle).height); pc.log('magin: %s', getComputedStyle(bodyEle).margin); pc.log('padding: %s', getComputedStyle(bodyEle).padding); pc.log('border-width: %s', getComputedStyle(bodyEle).borderWidth); pc.groupEnd(); }; pc.appendChild(iframe);
ul.pageconsole-output { display: flex; flex-direction: row-reverse; > li:nth-of-type(2) { padding-left: 0 !important; } > li:nth-of-type(1) { flex: 1; } & iframe { border: 15px solid gray; } }

当使用iframesrcdoc时,其load事件须点击2次才会被触发。故完全改用JavaScript动态添加的方式。

尽管iframe的边框宽度为15px,因其box-sizing的值为content-box,故并不影响其width属性值,仍为默认的300px

默认情况下,htmlmarginpadding均为0px

作为iframe的直接子元素,html的宽度将追随iframe的宽度,即,将在300px的范围内编排内容。由于我们为其设置了一条红边,且其计算盒子尺寸的方式为content-box,即widthheight的值均不包括边框厚度,故其width值应将父元素的宽度值300px减去左右两条边框线的厚度而为298px

如果在CSS面板中改变iframe宽度值,则看到html的宽度也随之改变。

至于body,其内容需在其父元素html的宽度298px内编排。但由于body的默认margin值为8px,故其width值为298 - 8 * 2 - 1 * 2 = 280px。也即,默认情况下,body的宽度值也受html的限制。

水平滚动时的溢出

作为最外层的容器,当子元素的宽度超出iframe的视口范围时,iframe会自动出现水平滚动条,以在水平方向上容纳所有子元素的内容。

let iframe = document.createElement('iframe'); iframe.srcdoc = `

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);
ul.pageconsole-output { display: flex; flex-direction: row-reverse; > li:nth-of-type(2) { padding-left: 0 !important; } > li:nth-of-type(1) { flex: 1; } & iframe { border: 15px solid gray; } }

p的内容在水平方向上溢出时,上面在body上将overflow的值设为clip,则在body(灰色背景)这一层上,超出灰色背景的文本将被裁切。但由于htmloverflow的值设为initial,其初始值即visible,故在html(黑色背景)这一层上仍能看到文本。

试将htmloverflow的值也设为clip,则将只看到灰色背景的文本。

htmlbodyoverflow的值均设为initial,恢复它们的初始值visible,则出现滚动条以显示全部的文本内容。

let iframe = document.createElement('iframe'); iframe.srcdoc = `

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);
ul.pageconsole-output { display: flex; flex-direction: row-reverse; > li:nth-of-type(2) { padding-left: 0 !important; } > li:nth-of-type(1) { flex: 1; } & iframe { border: 15px solid gray; } }

可以看到,向右移动滚动条,可视区域紧挨着文本的结尾就结束了,也即仅限于content-box,不包含marginpadding

对于iframeChromeoverflow的初始值为clipSafarioverflow的初始值为visible

clip意味着将可视区域限定在该元素的content-box内,而visible意味着超出content-box时仍可通过滚动条来查看。随着文本长度的增加,可视区域的宽度变大,但iframewidth值并不会改变。因此从这个意义上,个人认为,Safari在此点上的实现是精准的。

但实际上,即使在Safari中,将iframeoverflow的值设为clip,也不会发生裁切。可见规范对于出现滚动容器 (scroll container) 时如何应对的情形规定得还不是很明确。

scrollWidth

scrollWidth是指若在特定元素上设定滚动区域时该滚动区域的宽度。上面的滚动区域从红色的左边框开始,一直到黑色背景的右边。

由于body设有margin值,则bodyscrollWidth值比html的值要小。小多少?bodymargin加上htmlborder-width,则8 + 1 = 9Chrome计算出的差值为9,正确;Safari计算出的差值为10,不准确。

在水平方向上最大化视口

上面的滚动容器是建立在哪个元素之上?由于滚动区域包含了所有的黑色背景区域,显然,滚动容器建立在iframe之上。但这又与iframescrollWidth属性值仅为300px相矛盾。

因此,如果我们希望在不出现滚动条能自动显示全部内容,则需将iframewidth值设置为htmlbodyscrollWidth的值。由于body总是位于html的内部,则应取最外面的容器htmlscrollWidth值。

let iframe = document.createElement('iframe'); iframe.srcdoc = `

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);
ul.pageconsole-output { display: flex; flex-direction: row-reverse; > li:nth-of-type(2) { padding-left: 0 !important; } > li:nth-of-type(1) { flex: 1; } & iframe { border: 15px solid gray; } }

前面谈到,可视区域紧挨着文本的结尾就结束了,因此为保持左右边距的一致,还加上了body的右边距的值。

高度

html及body的height值会自动增大

let iframe = document.createElement('iframe'); iframe.srcdoc = `

A little long text that would exceed the width of 300px.

A demo div.
`; iframe.onload = (evt) => { pc.group('iframe'); pc.log('height: %s', getComputedStyle(iframe).height); pc.log('overflow: "%s"', getComputedStyle(iframe).overflow); pc.log('scrollHeight: %d', iframe.scrollHeight); pc.groupEnd(); pc.group('html'); let htmlEle = iframe.contentDocument.documentElement; pc.log('height: %s', getComputedStyle(htmlEle).height); pc.log('overflow: "%s"', getComputedStyle(htmlEle).overflow); pc.log('scrollHeight: %d', htmlEle.scrollHeight); pc.groupEnd(); pc.group('body'); let bodyEle = iframe.contentDocument.body; pc.log('height: %s', getComputedStyle(bodyEle).height); pc.log('magin: %s', getComputedStyle(bodyEle).margin); pc.log('overflow: "%s"', getComputedStyle(bodyEle).overflow); pc.log('scrollHeight: %d', bodyEle.scrollHeight); pc.groupEnd(); pc.group('p'); let pEle = iframe.contentDocument.querySelector('p'); pc.log('overflow: "%s"', getComputedStyle(pEle).overflow); pc.log('height: %s', getComputedStyle(pEle).height); pc.groupEnd(); iframe.style.width = `${htmlEle.scrollWidth + parseFloat(getComputedStyle(bodyEle).margin)}px`; }; pc.appendChild(iframe);
ul.pageconsole-output { display: flex; flex-direction: row-reverse; > li:nth-of-type(2) { padding-left: 0 !important; } > li:nth-of-type(1) { flex: 1; } & iframe { border: 15px solid gray; } }

需注意的是,在上一节中当文本内容超出htmlbodywidth值时,虽然出现了水平滚动条,但它们的width值不会改变。

与此不同的是,随着子元素的增加,htmlbodyheight值会自动增加。

此外,scrollHeightDOM元素的属性名,而不是CSS属性名。

最大化视口

观察垂直方向上的滚动区域,发现已自动包含上下两边的边距。因此iframeheight值只需取htmlscrollHeight的值即可。

let iframe = document.createElement('iframe'); iframe.srcdoc = `

A little long text that would exceed the width of 300px.

A demo div.
`; iframe.onload = (evt) => { pc.group('iframe'); pc.log('height: %s', getComputedStyle(iframe).height); pc.log('overflow: "%s"', getComputedStyle(iframe).overflow); pc.log('scrollHeight: %d', iframe.scrollHeight); pc.groupEnd(); pc.group('html'); let htmlEle = iframe.contentDocument.documentElement; pc.log('height: %s', getComputedStyle(htmlEle).height); pc.log('overflow: "%s"', getComputedStyle(htmlEle).overflow); pc.log('scrollHeight: %d', htmlEle.scrollHeight); pc.groupEnd(); pc.group('body'); let bodyEle = iframe.contentDocument.body; pc.log('height: %s', getComputedStyle(bodyEle).height); pc.log('magin: %s', getComputedStyle(bodyEle).margin); pc.log('overflow: "%s"', getComputedStyle(bodyEle).overflow); pc.log('scrollHeight: %d', bodyEle.scrollHeight); pc.groupEnd(); pc.group('p'); let pEle = iframe.contentDocument.querySelector('p'); pc.log('overflow: "%s"', getComputedStyle(pEle).overflow); pc.log('height: %s', getComputedStyle(pEle).height); pc.groupEnd(); iframe.style.width = `${htmlEle.scrollWidth + parseFloat(getComputedStyle(bodyEle).margin)}px`; iframe.style.height = '0px'; iframe.style.height = `${htmlEle.scrollHeight}px`; }; pc.appendChild(iframe);
ul.pageconsole-output { display: flex; flex-direction: row-reverse; > li:nth-of-type(2) { padding-left: 0 !important; } > li:nth-of-type(1) { flex: 1; } & iframe { border: 15px solid gray; } }

在设置高度值时,因iframe有默认高度值150px,将影响到html的默认scrollHeight值。当html的高度值小于150px时,自动伸缩的高度值也将使用iframe的默认高度值150px,从而导致不能自动压缩高度。因此在设置iframe的高度值前,应先其值设置为0px

CSS设置

在对iframe进行CSS设置时,需注意要分别进行两部分的设置。

let srcdoc = ` <body>
</body> `; let iframe = document.createElement('iframe'); iframe.srcdoc = srcdoc; pc.appendChild(iframe);

上面代码将生成以下的DOM树:

  • hoster stylesheets
  • hoster container element
    • iframe
      • #document
        • html
          • head
            • style
          • body
            • div

上面srcdoc中的内容,对应于上图中#document(也即iframe.contentDocument)中的内容,因此srcdoc所设置的CSS样式,只影响到#document下面的各个元素。无法及于iframe自身。

如果我们要对iframe元素进行较为复杂的CSS设置(因此不是仅简单地修改iframestyle属性值),如取消其边框线条的inset设置,同时加上圆角边框,则需在上图中的hoster stylesheets部分进行单独的设置。

let srcdoc = ` <body>
</body> `; let iframe = document.createElement('iframe'); iframe.srcdoc = srcdoc; pc.appendChild(iframe); pc.appendInlineStyle(` iframe { border: 1px solid gray; border-radius: 0.5em; } `);

pc.appendInlineStyle的作用,正在于在上图中的hoster stylesheets部分插入一个内联样式单,从而可以改变iframeCSS初始设置。

在iframe中玩转Color Scheme

Web应用中,由于iframe可以提供一个完全独立于其宿主、且又具备相对完整的HTML环境,因此具有非常高的应用价值。它可以让我们在不破坏宿主环境的情况下,灵活、自由地初始化一个全新的HTML环境,从而可以制作、演化出众多丰富多彩的Web组件。

本节中,我们利用iframe的特性,逐步建立起一个功能完善而全面的、基于color scheme的小组件,从而洞悉颜色主题切换的所有细节。

让浏览器自动帮我们设置颜色

function initIframe(srcdoc) { pc.appendInlineStyle(` ul.pageconsole-output { > li:has(iframe) { padding-left: 0em; display: flex; align-items: center; > iframe { border: 0.1px solid yellow; width: 100%; } } } `); let iframe = document.createElement('iframe'); iframe.srcdoc = srcdoc; pc.appendChild(iframe); } let srcdoc = ` <body>

Hello, Color Scheme.

</body> `; initIframe(srcdoc); pc.appendHTMLStr('

A paragraph outside iframe.

');

上面第一步,对iframe进行CSS设置,使其尺寸铺满父元素的空间。

第二步,设置iframecontentDocument的样式及实体内容。

通过设置color-scheme,且各个元素未设置任何与颜色相关的属性值,则浏览器根据操作系统中的颜色主题,自动帮我们设置各个元素的颜色。

在操作系统中切换颜色主题,可看到iframe中的p元素的colorbackground-color值也自动随之变化。

可以看到,iframeCSS设置只及于第一个p元素,不影响第二个p元素。这里插入第二个p元素的意义,仅在于确认iframeCSS影响范围,下面的各小节将删除它。

设置自定义颜色主题

function initIframe(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; pc.appendChild(iframe); } let srcdoc = ` <body>

Hello, Color Scheme.

</body> `; initIframe(srcdoc);

使用CSSlight-dark函数,为body及其子元素分别设置高亮模式及暗黑模式下的colorbackground-color属性值。自此,对这两个属性,浏览器不再帮我们自动设置其值。

获取并显示系统颜色主题

let srcdoc = ` <body>

Hello, Color Scheme.

System
User
</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的值仅设为lightdark,则浏览器将忽略操作系统发来的通知,直接使用所指定的颜色主题。

let srcdoc = ` <body>

Hello, Color Scheme.

System
User
</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函数中,为3radios设置点击响应事件,根据它们的值相应地设置color-scheme的值。

如果点击system,则网页中的输出区域可随操作系统中的颜色主题变化而变化;如果点击lightdark,则无论操作系统的设置为何值,输出区域立即根据用户选择调整相应的颜色主题。

使用本地存储保存用户的选择

如果希望记住用户的偏好,则可将用户的当前选择保存起来,然后再根据用户的偏好设置color-scheme的值。

作为示范,本例使用sessionStorage来保存。原因有二:

  1. 本站已经使用localStorage来保存用户偏好。因此例子不会影响本站应用的功能。
  2. 保存在sessionStorage的值只在浏览器当前标签页中有效。打开新的标签页,则需重新保存。这样方便调试各种状态。

若用在正式发布的应用中,只需将sessionStorage改为localStorage即可。

要实现此功能,需按3步骤进行。

第一步,先读取用户偏好,并根据其值设置color-scheme的值。

let userPreference = getUserPreference(); let srcdoc = ` ... `; initIframe(pc, srcdoc); function getUserPreference() { let userPrevColorTheme = sessionStorage.getItem('color-theme'); if (!userPrevColorTheme) { return 'system'; } else { return userPrevColorTheme; } }

在用户第一次加载网页时,sessionStorage尚未保存任何值,则返回system,根据操作系统的设置来决定颜色主题。

在初始化srcdoc时,如果变量userPreference的值为system,则将color-scheme的值设为light dark;否则直接取其值即可。

第二步,将用户偏好显示在界面中。

iframe.onload = (evt) => { let doc = iframe.contentDocument; autoHeight(iframe, doc); showSysInfo(doc); showUserPreference(doc); // added handleUserSelect(doc); }; function showUserPreference(doc) { let ele = doc.querySelector(`[name=user-color-scheme][value=${userPreference}]`); ele.toggleAttribute('checked'); }

第三步,修改handleUserSelect函数。

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); }; }); }

当用户在界面中选择偏好时,先设置相应的color-scheme的值,同时将用户偏好值保存进sessionStorage中。

当这一步完成之后,重新刷新网页,则上述第一步的代码即可取出所保存的用户偏好值,用以动态设置color-scheme的值。

下面是可运行的完整代码。

let userPreference = getUserPreference(); let srcdoc = ` <body>

Hello, Color Scheme.

System
User
</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,则输出区域的颜色主题跟随操作系统的颜色主题的设置而改变。在操作系统中切换颜色主题,输出区域自动随之变亮或变暗。

点击lightdark,再刷新网页,将会直观地看到,在本例中浏览器记住了我们的选择,不管操作系统或是网页的整体颜色主题是什么,本例的输出区域均仅会根据用户选择进行输出。

参考资源

  1. The iframe element
  2. MDN: <iframe>: The Inline Frame element