iframe遵守同源策略,只有父页面与嵌套页面来自同一个域名,两者才能通信。所以首先需要进行同域代理。
1、怎么实现同域代理
- 在服务器端设置代理,将请求转发给目标地址。这样即使子页面本身可能需要跨域访问其他资源,也可以通过同域代理服务器来处理这些请求。例如,在
nginx中配置代理:
server {
listen 80;
server_name yourdomain.com;
location /proxy/ {
proxy_pass http://targetdomain.com/;
proxy_set_header Host targetdomain.com;
add_header X-Frame-Options ALLOWALL;
}
}
- 然后,子页面可以通过
/proxy/路径来访问targetdomain.com的资源。
2、如何不改动子页面代码的情况下监听iframe嵌套页面的路由变化
2.1、代理iframe的 History 对象(对跨域不适用)
-
原理: 通过代理
iframe的history.pushState和replaceState方法,你可以在这些方法被调用时获取到路由的变化。 -
在父页面中设置
proxy:
async mounted() {
this.watchIframeUrl();
},
methods: {
/**
* 监听iframe的URL变化
* 该方法通过劫持iframe窗口的历史记录API来实现
* 主要用于捕获pushState和replaceState的操作
*/
watchIframeUrl() {
// 获取iframe元素
const iframe = this.$refs.iframe;
// 获取iframe的窗口对象
const iframeWindow = iframe.contentWindow;
// 备份原始的pushState方法
const originalPushState = iframeWindow.history.pushState;
// 备份原始的replaceState方法
const originalReplaceState = iframeWindow.history.replaceState;
// 覆盖pushState方法,以监听URL的变化
iframeWindow.history.pushState = function (...args) {
// 调用原始的pushState方法,并传递所有参数
originalPushState.apply(iframeWindow.history, args);
// 打印pushState操作的参数和状态变化
console.log('iframe pushState: ', args, args[2]);
// 检查特定的URL,如果有匹配,则执行相关操作
if (
args[2] ===
'/eolink/router/api/message/5247eb64-011d-055a-fe8f-741cfa8fe78d'
) {
// 执行与特定URL相关的操作
console.log('push');
}
};
// 覆盖replaceState方法,以监听URL的变化
iframeWindow.history.replaceState = function (...args) {
// 调用原始的replaceState方法,并传递所有参数
originalReplaceState.apply(iframeWindow.history, args);
// 打印replaceState操作的参数和状态变化
console.log('iframe replaceState: ', args, args[2]);
// 检查特定的URL,如果有匹配,则执行相关操作
if (args[2] === '/eolink/router/api/group/list') {
// 执行与特定URL相关的操作
console.log('replace');
}
};
}
}
但同样的,这种方法在跨域情况下是无法实现的。
- 下图是路由发生改变时的
console.log
2.2、通过 MutationObserver 观察 iframe 的 URL 变化
- 原理:
MutationObserver可以用于监听 DOM 元素的属性变化。你可以通过监听iframe的src属性的变化来判断其内部页面的路由变化。 - 步骤:
- 父页面监听
iframe的src变化:
const iframe = document.querySelector('iframe');
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
console.log('iframe src changed to: ', iframe.src);
}
});
});
observer.observe(iframe, { attributes: true });
这种方法的局限性是:如果子页面是单页应用程序(SPA) ,它的 src 可能不会变,所以这个方法无法捕捉内部的路由变化。
2.3、定时轮询 iframe 的 URL
-
原理: 定时获取
iframe的window.location,通过对比前后的 URL 来判断路由是否发生变化。 -
父页面定时检查
iframe的 URL:
data() {
retuern {
intervalId: null
}
}
mounted() {
const iframe = this.$refs.iframe;
const pollingInterval = 1000; // 设置轮询间隔时间(毫秒)
// 检查 iframe 的 URL
const checkIframeUrl = () => {
// 当前iframe的src
const currentUrl = iframe.contentWindow.location.href;
// 具体的逻辑
const baseUrl = `${window.location.origin}/ang_kai-db`;
if (
![
`${baseUrl}/user-info`,
`${baseUrl}/user-password`,
`${baseUrl}/user-manage`
].includes(currentUrl)
) {
this.setCss();
}
};
// 启动轮询
this.intervalId = setInterval(checkIframeUrl, pollingInterval);
}
destroyed() {
this.cleanup();
},
methods: {
cleanup() {
clearInterval(this.intervalId);
},
}
这个方法虽然简单直接,但有一定的性能开销,而且如果 iframe 中的页面存在跨域限制(如 SameSite 属性),你将无法访问 contentWindow 的属性,这种方法就无法使用。
3、iframe扩展
3.1、contents()获取iframe嵌套页面的dom树进行模拟点击
这个方法在我的一个需求中使用到了。 需求是这样的:
iframe嵌套了子页面,第一,需要将子页面的顶部菜单栏进行隐藏;第二,需要将子页面顶部菜单栏右上角的一些功能移到父页面的右上角,如下图所示:
-
contents()是jQuery提供的一个方法,用于获取指定元素的子节点,包括文本节点和子元素节点。它可以返回一个包含所有子节点的jQuery对象。详细说明可点击链接查看 -
contents()在处理iframe元素时特别有用,它可以获取iframe的文档对象(document),从而可以访问iframe中的整个 DOM 树。
当你需要操作 iframe 中的元素时,先使用 contents() 获取 iframe 内部的文档对象,然后在这个文档对象上使用 jQuery 方法来操作其内容。例如:
const iframeContent = $('#myIframe').contents();
const iframeElement = iframeContent.find('#childElement');
iframeContent.find('#childElement').click() // 模拟点击
这里的 contents() 用于获取 iframe 的文档对象,然后在该文档对象内使用 find() 查找特定的元素。