前端实现修改嵌套内部iframe主题样式

2,367 阅读1分钟

需求:最近公司新开一个项目要求引入外部实现主题样式切换,此文主要介绍如何实现修改嵌套内部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类名并且增加了相关样式