需求:最近公司新开一个项目要求引入外部实现主题样式切换,此文主要介绍如何实现修改嵌套内部iframe主题样式的几种方案和方式,包括跨域和同源的情况下如何实现该需求
前言
HTMLIFrameElement.contentDocument
使用此方法可以获取iframe中的dom对象,如果 iframe 和 iframe 的父文档是同源,则返回 一个 Document
(即内嵌框架的嵌套浏览上下文中的活动文档),否则返回null
。(摘抄MDN)
在同源下修改iframe样式
方法1:直接获取到iframe元素进行修改
const iframeDocument = document.querySelector("iframe").contentDocument;
iframeDocument.body.style.backgroundColor = "blue";
// This would turn the iframe blue.
通过以上的方法,能够直接把iframe中body的背景颜色更改为blue
方法2:在iframe的head中插入相关样式表
// 页面上有一个id为i1的iframe,它嵌入的是同源的文件 child.html
<iframe id="i1" src="./child.html" frameborder="0" width="100%" height="400"></iframe>
// 借助jQuery在iframe加载后,向其内部插入style.css
$('#i1').load(function () {
let cssLink = document.createElement("link");
cssLink.href = "style.css";
cssLink.rel = "stylesheet";
cssLink.type = "text/css";
$('#i1')
.contents().find("head")
.append($('<link rel="stylesheet" type="text/css" href="style.css">')
);
});
// style.css
body {
background-color: aqua;
}
非同源(跨域)情况下修改iframe的元素样式
该方法主要利用到如下两种方法来实现,详见MDN介绍,这边不做具体的介绍
原理:父级页面中引入了一个不同域名下的iframe,第一,需要在父级页面发送信息,第二,在iframe页面内监听并处理信息,来间接的修改样式。这是为了保证跨域通信的安全,详细内容参考 MDN。
下面介绍具体做法:
父级页面引入了一个跨域的iframe,id为ifm
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试iframe</title>
</head>
<body>
<button onclick="activateTheme('gray')">切换灰色主题</button>
<iframe id="ifm" src="http://xxxxx:8545"></iframe>
</body>
<script>
//点击切换主题的方法
function activateTheme(theme) {
// console.log(theme)
var iframe = document.getElementById("ifm");
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(theme, "*");
}
}
</script>
</html>
当点击切换主题按钮后,我们传递了一个gray灰色主题的消息,具体代码被引入的iframe html代码粘贴如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>React App</title>
<script defer="defer" src="/static/js/main.87b8b4a6.js"></script>
<link href="/static/css/main.3e093513.css" rel="stylesheet" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
<script>
window.addEventListener("message",
function (event) {
document.body.classList = []
document.body.classList.add(`skin-${event.data}`)
}, false
);
</script>
</html>
可以看出我们能够在子级iframe接收到父级传递过来的消息,并且修改了iframe内部的body class类名并且增加了相关样式