嵌套网页 - Iframe
核心原生作用
- iframe 的核心原生作用是 "在当前页面嵌入独立的 HTML 文档(嵌套网页)"
-
iframe 是浏览器同源策略下,唯一能合法嵌入跨域页面并实现跨域通信的载体
-
iframe语法 : 该URL指向不同的网页
html//iframe 标签 可以在父页面中嵌套子页面 //iframe src的地址 就是嵌套子页面的地址 <iframe src="URL"></iframe>
-
标签属性
-
iframe设置高度与宽度
- height 和 width 属性用来定义iframe标签的高度与宽度
- 属性默认以像素为单位, 但是你可以指定其按比例显示 (如:"80%")
html<iframe src="demo_iframe.htm" width="200" height="200"></iframe> -
iframe移除边框
-
frameborder 属性用于定义iframe表示是否显示边框。
-
设置属性值为 "0" 移除iframe的边框
html<iframe src="demo_iframe.htm" frameborder="0" style="border: none; outline: none;" <!-- 兜底:防止浏览器默认样式残留 --> ></iframe>
-
-
使用iframe来显示目标链接页面
- iframe可以显示一个目标链接的页面
- 目标链接的属性必须使用iframe的属性,如下实例:
html<iframe src="demo_iframe.htm" name="iframe_a"></iframe> <p><a href="http://www.bxxx.cn" target="iframe_a">bxxx</a></p>
| align | - left - right - top - middle - bottom | 不赞成使用。请使用样式代替。 规定如何根据周围的元素来对齐此框架。 |
|---|---|---|
| frameborder | - 1 - 0 | 规定是否显示框架周围的边框。 |
| height | - pixels - % | 规定 iframe 的高度。 |
| longdesc | URL | 规定一个页面,该页面包含了有关 iframe 的较长描述。 |
| marginheight | pixels | 定义 iframe 的顶部和底部的边距。 |
| marginwidth | pixels | 定义 iframe 的左侧和右侧的边距。 |
| name | frame_name | 规定 iframe 的名称。 |
| sandbox | - "" - allow-forms - allow-same-origin - allow-scripts - allow-top-navigation | 启用一系列对 中内容的额外限制。 |
| scrolling | - yes - no - auto | 规定是否在 iframe 中显示滚动条。 |
| seamless | seamless | 规定 看上去像是包含文档的一部分。 |
| src | URL | 规定在 iframe 中显示的文档的 URL。 |
| srcdoc | HTML_code | 规定在 中显示的页面的 HTML 内容。 |
| width | - pixels - % | 定义 iframe 的宽度。 |
| allowFullScreen | -true -false | 授权 iframe 内部的页面 / 元素触发「全屏模式」(比如视频全屏播放、PDF 全屏预览、在线编辑器全屏编辑等) |
属性示例
- 图片

- 代码
javascript
<iframe width="160" height="180" frameborder="0" scrolling="no"
src="//chongzhi.jd.com/jdhome-czindex-2017.html"></iframe>
常见可监听事件
| 事件名 | 触发场景 | 实用价值 |
|---|---|---|
| load | iframe 页面(包括跨域)加载完成(资源、DOM 都加载完毕) | 最常用,用于触发高度自适应、初始化逻辑 |
| error | iframe 加载失败(如地址错误、404、网络中断) | 兜底处理(显示错误提示、重试按钮) |
| abort | iframe 加载被中断(如用户手动取消、浏览器终止请求) | 异常场景处理 |
| loadstart | iframe 开始加载资源 | 显示加载中状态 |
| progress | iframe 加载资源过程中(如图片 / 脚本下载) | 进度条展示(跨域下仅能感知 "加载中",无法获取具体进度) |
不可直接监听的事件(受同源策略限制)
这些是 iframe 内部页面 的 DOM/JS 事件,由于浏览器同源策略禁止父页面访问跨域 iframe 的 contentDocument/contentWindow,因此无法直接绑定监听。
-
常见不可直接监听的事件
内部页面的 click、scroll、input、change、resize 等 DOM 事件;
内部页面的自定义 JS 事件(如 customEvent);
内部页面的 window.resize、document.visibilitychange 等全局事件;
内部页面元素的属性 / 样式变化(如内容高度变化、按钮状态变化)。 -
替代方案:postMessage 传递内部事件(核心解决方法)
通过「内部页面主动发送事件信息 + 父页面监听消息」的方式,实现跨域事件监听,步骤如下:
步骤 1:内部页面(被嵌套的跨域页面)- 监听自身的目标事件,触发时通过 postMessage 向父页面发送事件信息:
js// 内部页面的 JS 代码(无论是否是 React,核心是 postMessage) // 示例 1:监听内部按钮点击事件 document.querySelector('#inner-btn').addEventListener('click', () => { // 向父页面发送点击事件信息(严格指定父域名,避免泄露) window.parent.postMessage( { type: 'innerClick', data: { btnId: 'inner-btn', time: Date.now() } }, 'https://父页面的域名.com' // 生产环境务必指定具体域名,不要用 \* ); }); // 示例 2:监听内部页面滚动事件(防抖避免频繁发送) const debounce = (fn, delay) => { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; }; window.addEventListener('scroll', debounce(() => { window.parent.postMessage( { type: 'innerScroll', data: { scrollTop: document.documentElement.scrollTop } }, 'https://父页面的域名.com' ); }, 100)); // 示例 3:监听内部内容高度变化(用于高度自适应) const sendHeight = () => { const height = document.querySelector('#content').offsetHeight; window.parent.postMessage( { type: 'innerHeightChange', height }, 'https://父页面的域名.com' ); }; // 页面加载/内容变化时发送高度 window.addEventListener('load', sendHeight); document.querySelector('#expand-btn').addEventListener('click', sendHeight);
步骤 2:React 父页面(监听 message 事件)
js
import { useRef, useEffect, useState } from 'react';
const CrossDomainIframe = () => {
const [iframeHeight, setIframeHeight] = useState('800px');
const [innerEvents, setInnerEvents] = useState<Array<{ type: string; data: any }>>([]);
// 监听跨域 iframe 发送的 message 事件
useEffect(() => {
const handleMessage = (e: MessageEvent) => {
// 关键:验证消息来源,防止恶意域名注入
const allowedOrigins = ['https://内部页面的域名.com'];
if (!allowedOrigins.includes(e.origin)) return;
// 处理不同类型的内部事件
switch (e.data.type) {
case 'innerHeightChange':
// 同步内部页面高度
setIframeHeight(`${e.data.height + 20}px`);
break;
case 'innerClick':
// 记录内部按钮点击事件
setInnerEvents(prev => [...prev, e.data]);
console.log('内部按钮点击:', e.data);
break;
case 'innerScroll':
console.log('内部页面滚动:', e.data.scrollTop);
break;
default:
break;
}
};
// 监听全局 message 事件
window.addEventListener('message', handleMessage);
// 清理监听
return () => {
window.removeEventListener('message', handleMessage);
};
}, []);
return (
<div>
<iframe
src="https://内部页面的域名.com"
width="100%"
height={iframeHeight}
frameBorder="0"
allowFullScreen={true}
scrolling="no"
/>
//展示内部页面传递的事件
<div>
<h4>内部页面事件记录:</h4>
{innerEvents.map((event, idx) => (
<div key={idx}>{event.type} - {JSON.stringify(event.data)}</div>
))}
</div>
</div>
);
};
export default CrossDomainIframe;
iframe跨域&通信
跨域通信:postMessage跨域
- 同源跨域
- 当前页面 和 被嵌入的页面 同源
- 非同源(跨域)场景下:
- 可以直接监听 iframe 元素本身的原生 DOM 事件(如加载、失败),不受同源策略限制;
- 无法直接监听 iframe 内部页面的 DOM/JS 事件(如内部按钮点击、滚动、输入框变化),需通过 postMessage 跨域通信间接监听;
iframe组件
ts
const AxxIframe: React.FC<{
src: string;
width?: number | string;
height?: number | string;
query?: string;
}> = ({ src, width = '100%', height = 1000, query }) => {
return (
<iframe
src={src + (query ? `&query=${encodeURIComponent(query)}` : '')}
width={width}
height={height}
allowFullScreen={true}
/>
);
};
export default AxxIframe;
---------
import { useRef, useState, useEffect } from "react";
const AxxIframe: React.FC<{
src: string,
width?: number | string,
height?: number | string,
query?: string,
}> = ({ src, width = "100%", height = 1000, query }) => {
const iframeRef = useRef < HTMLIFrameElement >(null);
const [iframeHeight, setIframeHeight] = useState < string | number > (height); // 初始高度
// 计算并设置 iframe 高度
const calculateIframeHeight = () => {
if (!iframeRef.current) {
return;
}
try {
// 1. 获取iframe内部页面的document对象
const iframeDoc =
iframeRef.current.contentDocument ||
iframeRef.current.contentWindow?.document;
if (!iframeDoc) {
return;
}
// 2. 定位内部页面的「内容区域」(替换为实际的内容区域选择器,如#content)
const contentArea: any = iframeDoc.querySelector(
"#pageContainer"
);
// 兜底:若找不到内容区域,取整个body高度
const targetHeight: any =
contentArea?.offsetHeight || iframeDoc.body.offsetHeight;
// 3. 设置高度(加少量冗余,避免滚动条)
if (typeof targetHeight === "number") {
setIframeHeight(`${targetHeight + 20}px`);
}
} catch (e) {
console.error("获取iframe高度失败(可能跨域):", e);
// 跨域兜底:可临时写死一个常用高度
setIframeHeight("800px");
}
};
useEffect(() => {
const iframe = iframeRef.current;
console.log("iframe: ", iframeRef);
if (!iframe) {
return;
}
// 监听iframe加载完成
iframe.addEventListener("load", calculateIframeHeight);
// eslint-disable-next-line consistent-return
return () => {
iframe.removeEventListener("load", calculateIframeHeight);
};
}, [src, query]);
return (
<iframe
ref={iframeRef}
src={src + (query ? `&query=${encodeURIComponent(query)}` : "")}
width={width}
height={iframeHeight}
style={{ maxHeight: 1000 }}
/>
);
};
export default AxxIframe;
// 获取iframe高度失败(可能跨域): SecurityError: Failed to read a named property 'document' from 'Window': Blocked a frame with origin "https://xxxx.net" from accessing a cross-origin frame.
// SecurityError: Failed to read a named property 'document' from 'Window': Blocked a frame with origin "https://xxxx-row.net" from accessing a cross-origin frame.