iframe 是 HTML 中用于在当前页面内嵌入另一个独立文档的标签,形成“页面中的页面”。其核心价值在于隔离内容渲染环境,但需严格处理跨域安全与性能问题。
嵌入独立文档:在当前页面中加载并显示另一个 HTML 页面、PDF 或第三方资源(如地图、视频),形成独立的浏览上下文(拥有自己的 DOM、JavaScript 执行环境和会话历史)。
内容隔离:嵌入内容与父页面互不干扰(CSS 样式、JavaScript 变量默认不共享),避免第三方代码污染主页面逻辑。
现代浏览器(如 Chromium)通常为每个 iframe 分配独立的渲染进程或 JS 堆,导致内存占用显著增加(3 个 iframe 可使内存消耗提升 30%~50%)
iframe 基础属性
[Exposed=Window]
interface HTMLIFrameElement : HTMLElement {
[HTMLConstructor] constructor();
[CEReactions, ReflectURL] attribute USVString src;
[CEReactions] attribute (TrustedHTML or DOMString) srcdoc;
[CEReactions, Reflect] attribute DOMString name;
[SameObject, PutForwards=value, Reflect] readonly attribute DOMTokenList sandbox;
[CEReactions, Reflect] attribute DOMString allow;
[CEReactions, Reflect] attribute boolean allowFullscreen;
[CEReactions, Reflect] attribute DOMString width;
[CEReactions, Reflect] attribute DOMString height;
[CEReactions] attribute DOMString referrerPolicy;
[CEReactions] attribute DOMString loading;
readonly attribute Document? contentDocument;
readonly attribute WindowProxy? contentWindow;
Document? getSVGDocument();
// also has obsolete members
};
根据 HTML 标准,<iframe> 元素的默认尺寸为:
- 宽度:
300像素 - 高度:
150像素
HTML 属性 height 的值默认单位是像素,不需要写 px。
<iframe src="https://juejin.cn/" frameborder="0" width="100%" height="100"></iframe>
/* 全局 iframe 样式 */
iframe {
height: 100vh;
}
src 属性
指定要加载的文档 URL。可以是同源或跨域的绝对/相对路径。
<iframe src="https://www.baidu.com" frameborder="0"></iframe>
百度服务器在它的响应中设置了一个 Content Security Policy (CSP) 指令,明确禁止了你的网页将
https://www.baidu.com 嵌入到 iframe 中。
srcdoc
直接书写 HTML 代码作为 iframe 内容,优先级高于 src。适用于展示简单片段,避免额外网络请求。
<iframe frameborder="0" srcdoc="<h2>Hello</h2><p>这是内嵌内容</p>"></iframe>
name 属性
用于 <a> 或 <form> 的 target 属性,或 JavaScript 中的 window.open 指定目标。
<iframe name="myFrame"></iframe>
<a href="page.html" target="myFrame">在 iframe 中打开</a>
sandbox 属性
sandbox 对 iframe 内容施加一系列限制,为空时启用所有限制。通过添加标志位放宽特定权限。
| 标志位 | 作用 |
|---|---|
allow-same-origin | 允许 iframe 将其来源视为与父页面同源(否则强制为唯一独立源) |
allow-scripts | 允许执行 JavaScript,包括 alert、postMessage 等 |
allow-forms | 允许表单提交 |
allow-popups | 允许打开弹窗(window.open) |
allow-top-navigation | 允许 iframe 将最顶层窗口(top)导航到新 URL |
allow-top-navigation-by-user-activation | 只有在用户交互(点击等)后才允许顶层导航 |
allow-modals | 允许模态框(alert, confirm, prompt) |
allow-downloads | 允许下载文件 |
allow-presentation | 允许 iframe 启动演示请求 |
1、sandbox, 空值 (sandbox 或 sandbox=""):启用全部限制。
<iframe src="page.html" frameborder="0" sandbox></iframe>
页面中的脚本执行被阻止,因为该文档所在的 iframe 启用了沙箱(sandbox),但没有授予 allow-scripts 权限。
2、设置 allow-scripts
page.html 文件
<body>
<h1>这是一个page页面</h1>
</body>
<script>
console.log("这是一个page页面");
</script>
<iframe src="page.html" frameborder="0" sandbox="allow-scripts"></iframe>
- 根据 HTML 规范,当一个文档被沙箱化且没有
allow-same-origin时,浏览器会将其强制视为一个唯一的、不透明的源(opaque origin),与任何实际 URL 的源(包括父页面和自身原本的源)完全隔离。 - 在此模式下,所有与源相关的 Web 存储 API(
sessionStorage、localStorage、IndexedDB、Cookies等)都无法访问,因为访问它们需要明确的源标识。
3、设置 allow-same-origin
- 当
sandbox不存在时,同源 iframe 可以通过contentDocument访问父页面的 DOM。 - 有了
sandbox但没有allow-same-origin,即使同源也被视为跨域,无法访问父页面。
同时添加 allow-scripts 和 allow-same-origin 会使沙箱形同虚设,因为 iframe 内的脚本可以:
- 读取和修改父页面的 DOM
- 窃取父页面的 localStorage、Cookie、sessionStorage
- 发送带有用户凭据的请求
- 执行任意操作,就像没有沙箱一样
4、设置 allow-modals
Ignored call to 'alert()'. The document is sandboxed, and the 'allow-modals' keyword is not set.
// page.html
alert("这是一个page页面");
5、设置 allow-popups
<body>
<!-- page.html -->
<h1>这是一个page页面</h1>
<button id="btn-click">点击我</button>
</body>
<script>
const btn = document.getElementById("btn-click");
btn.addEventListener("click", () => {
window.open("https://www.baidu.com");
});
</script>
Blocked opening 'https://www.baidu.com/' in a new window because the request was made in a sandboxed frame whose 'allow-popups' permission is not set.
6、设置 allow-forms
<form id="form-click" action="https://localhost:8443/api/user" method="post">
<input type="text" name="name" value="lili" />
<input type="text" name="email" value="lili@example.com" />
<input type="submit" value="点击我" />
</form>
Blocked form submission to 'https://localhost:8443/api/user' because the form's frame is sandboxed and the 'allow-forms' permission is not set.
loading 属性
lazy:延迟加载,当 iframe 进入视口附近时才加载。eager:立即加载(默认)
allow 属性
allow 属性用于控制嵌入在 <iframe> 中的内容可以使用哪些浏览器功能(如地理位置、摄像头、麦克风、全屏、自动播放等)。
| 功能名 | 描述 |
|---|---|
geolocation | 地理位置 |
camera | 摄像头 |
microphone | 麦克风 |
fullscreen | 全屏请求 |
payment | 支付请求 |
autoplay | 自动播放媒体(带声音) |
encrypted-media | EME(加密媒体扩展) |
midi | MIDI 设备 |
clipboard-read / clipboard-write | 剪贴板读写 |
usb | WebUSB |
bluetooth | Web Bluetooth |
xr-spatial-tracking | WebXR |
<iframe
allow="geolocation 'src'"
srcdoc='
<html>
<body>
<button onclick="navigator.geolocation.getCurrentPosition(pos => alert(pos.coords.latitude))">
获取位置
</button>
</body>
</html>
'
>
</iframe>
| 属性 | 作用 | 控制对象 |
|---|---|---|
sandbox | 对 iframe 内容施加安全限制(如禁止脚本、表单、弹窗等) | 基本网页行为 |
allow | 控制 iframe 内容能否使用浏览器强大功能(设备 API) | 设备/浏览器能力 |
iframe 与父页面通信(同源 & 跨源)
父子页面通信/同源 (无 sandbox)
父页面 → iframe 内部:
父页面可以通过 iframe.contentDocument 获取 iframe 的文档对象,进而操作其 DOM。
iframe.contentWindow, iframe 元素内部的window对象iframe.contentDocument, iframe 元素内部的document对象
// 父页面
<iframe src="page.html"></iframe>
const iframe = document.querySelector("iframe");
console.log('父winodw', iframe.contentWindow)
// page.html iframe 页面
<h1>这是一个page页面</h1>
console.log('page.html-window', window)
iframe 内部 → 父页面:
iframe 内的脚本可以通过 window.parent.document 访问父页面的 DOM。
window.parent, 当前 iframe 的直接父级windowwindow.top, 最顶层窗口(浏览器标签页的 window),多层嵌套时直达顶层
父子页面通信/同源 (有 sandbox)
如果 iframe 有 sandbox 属性(且没有 allow-same-origin),则浏览器会强制将 iframe 的内容视为唯一不透明源,即使实际 URL 与父页面同源,也会被当作跨域处理。
- 父页面的
contentDocument访问会因跨域安全错误而抛出异常。 - iframe 内部也无法访问
window.parent的 DOM。
父页面能否操作 iframe 内部,取决于同源策略,而非 sandbox。 sandbox(不带 allow-same-origin)会让同源 iframe 被当作跨源处理,从而阻止父页面读写 iframe 的 DOM
跨域通信
三种 sandbox 配置对比
| sandbox | iframe origin | 直接访问 DOM | postMessage targetOrigin | event.origin |
|---|---|---|---|---|
| 无 sandbox | 保持原始 origin | 同源可访问 | 精确 origin | "http://localhost:3000" |
| allow-scripts 和 allow-same-origin | 保持原始 origin | 同源可访问 | 精确 origin | "http://localhost:3000" |
allow-scripts | opaque origin | 跨域拦截 | 必须用 "*" | "null" |
主页面
<body>
<h1>这是一个 顶层 页面</h1>
<button id="sendMessageBtn">发送消息</button>
<iframe src="parent.html" width="100%" sandbox="allow-scripts"></iframe>
</body>
<script>
window.aiframeFlag = "top";
console.log("这是一个 顶层 页面");
const iframe = document.querySelector("iframe");
console.log("在顶层-获取iframe.contentWindow", iframe.contentWindow);
const sendMessageBtn = document.querySelector("button");
sendMessageBtn.addEventListener("click", () => {
const iframeWindow = iframe.contentWindow;
console.log("在顶层-获取iframeWindow", iframeWindow.postMessage);
iframeWindow.postMessage("hello iframe,i am top", "*");
});
// 监听 iframeWindow 发送的消息
window.addEventListener("message", (event) => {
// 过滤掉自己发送的消息
if (event.source === window) return;
console.log("在顶层-收到消息:", event.data, "来自:", event.origin);
});
</script>
中间层
<body>
<h1>这是一个parent页面</h1>
<button id="sendMessageBtn">发送消息</button>
<iframe
src="sub.html"
height="300"
width="90%"
sandbox="allow-scripts"
></iframe>
</body>
<script>
window.aiframeFlag = "parent";
console.log("这是一个parent 页面");
const parentWindow = window.parent;
console.log("在parent-获取parent", parentWindow);
const sendMessageBtn = document.querySelector("button");
sendMessageBtn.addEventListener("click", () => {
// 发送消息到 parentWindow
parentWindow.postMessage("hello top am parent", "*");
const iframeWindow = iframe.contentWindow;
iframeWindow.postMessage("hello iframe,i am parent", "*");
});
// 监听 parentWindow 发送的消息
window.addEventListener("message", (event) => {
// 过滤掉自己发送的消息
if (event.source === window) return;
console.log("在parent-收到消息:", event.data, "来自:", event.origin);
});
const iframe = document.querySelector("iframe");
console.log("在parent-获取iframe.contentWindow", iframe.contentWindow);
</script>
sub 页面
<body>
<h1>这是一个sub页面</h1>
</body>
<script>
window.aiframeFlag = "sub";
window.addEventListener("message", (event) => {
console.log("在sub-收到消息:", event.data, "来自:", event.origin);
});
</script>
示例 iframe 内的表单提交后将数据传递给父页面
流程:
- 父页面与 iframe 不同源(跨域)
- iframe 内部包含一个表单,用户填写后提交
- 提交后,iframe 需要将表单数据(或处理结果)发送给父页面
允许 iframe 执行脚本和提交表单 ,sandbox 属性必须包含:
allow-forms:允许表单提交allow-scripts:允许运行 JavaScript(用于postMessage)
父页面
<body>
<h1>这是一个 顶层 页面</h1>
<button id="sendMessageBtn">发送消息</button>
<iframe
src="parent.html"
width="100%"
sandbox="allow-scripts allow-forms"
></iframe>
</body>
<script>
window.aiframeFlag = "top";
console.log("这是一个 顶层 页面");
const iframe = document.querySelector("iframe");
console.log("在顶层-获取iframe.contentWindow", iframe.contentWindow);
const iframeWindow = iframe.contentWindow;
const sendMessageBtn = document.querySelector("button");
sendMessageBtn.addEventListener("click", () => {
console.log("在顶层-获取iframeWindow", iframeWindow.postMessage);
iframeWindow.postMessage("hello iframe,i am top", "*");
});
// 监听 iframeWindow 发送的消息
window.addEventListener("message", (event) => {
// 过滤掉自己发送的消息
if (event.source === window) return;
console.log("在顶层-收到消息:", event.data, "来自:", event.origin);
iframeWindow.postMessage("已收到form 数据", "*");
});
</script>
子页面
拦截表单的默认提交行为(event.preventDefault())
<body>
<h1>这是一个parent页面</h1>
<form id="myForm">
<input
type="text"
id="input"
name="name"
placeholder="请输入姓名"
value="lili"
/>
<input
type="text"
id="email"
name="email"
placeholder="请输入邮箱"
value="123@qq.com"
/>
<button type="submit" id="submitBtn">发送发送消息</button>
</form>
</body>
<script>
window.aiframeFlag = "parent";
console.log("这是一个parent 页面");
document.getElementById("myForm").onsubmit = (e) => {
// 阻止表单默认提交行为
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries());
// 发送给父页面
window.parent.postMessage(
{
type: "formSubmit",
payload: data,
},
"*",
); // 精确指定父页面源
};
// 监听 parentWindow 发送的消息
window.addEventListener("message", (event) => {
// 过滤掉自己发送的消息
if (event.source === window) return;
console.log("在parent-收到消息:", event.data, "来自:", event.origin);
});
</script>
iframe 安全(XSS、点击劫持、沙箱)
| 风险 | 描述 | 影响 |
|---|---|---|
| 点击劫持 (Clickjacking) | 攻击者将目标网站透明 iframe 覆盖在诱饵按钮上,诱导用户点击 | 窃取凭证、执行非授权操作 |
| 恶意 iframe 窃取父页面信息 | 嵌入的恶意页面通过脚本读取父页面 DOM、Cookie 等 | 数据泄露、XSS |
| 沙箱逃逸 | 未正确配置 sandbox 时,恶意内容可执行脚本、提交表单 | 钓鱼、植入恶意代码 |
点击劫持
点击劫持(Clickjacking,又称 “UI 覆盖攻击”)是一种针对用户视觉误导的恶意攻击,核心原理是 攻击者通过透明或半透明的 iframe 嵌套目标网站,叠加在恶意页面的诱导元素(如按钮、链接)上方,诱导用户点击看似无害的内容时,实际点击了目标网站的敏感操作按钮(如登录、支付、授权、删除),从而骗取用户执行未授权操作。
预防点击劫持
- 后端可以通过设置 HTTP 响应头 来禁止浏览器将当前页面嵌入到
<iframe>中,从而有效防止点击劫持(Clickjacking)和盗链嵌入。
响应头 X-Frame-Options
| 值 | 效果 |
|---|---|
DENY | 完全禁止被任何页面嵌入(包括同域名) |
SAMEORIGIN | 只允许被同源(相同协议+域名+端口)页面嵌入 |
响应头 Content-Security-Policy
| 指令 | 含义 |
|---|---|
frame-ancestors 'none' | 禁止任何页面嵌入 |
frame-ancestors 'self' | 只允许同源页面嵌入 |
frame-ancestors https://example.com | 只允许特定域名嵌入 |
禁止任何域名
允许同源页面
示例 vue3 项目 proxy 代理
允许指定域名
恶意 iframe 窃取父页面信息
当网页嵌入一个恶意 iframe 时,该 iframe 可能会尝试读取父页面中的敏感数据(如 Cookie、localStorage、DOM 内容、表单输入等)。这种行为能否成功,取决于浏览器的同源策略和 iframe 的 sandbox 属性配置。
沙箱逃逸
所谓 “沙箱逃逸” ,就是指嵌入式内容(如广告、插件、用户生成内容)突破了sandbox属性设定的安全边界,逃逸到外层父页面,甚至获取更高权限的操作系统能力。
在 HTTP 响应头中添加该指令,其基础语法如下:
Content-Security-Policy: sandbox; 或 Content-Security-Policy: sandbox <value>;
默认情况下(值为空),沙箱会启用所有限制。
allow-forms: 允许沙盒内的页面提交表单。allow-modals: 允许页面使用alert()、confirm()等方法打开模态窗口。allow-orientation-lock: 允许页面锁定屏幕方向。allow-pointer-lock: 允许页面使用指针锁定 API。allow-popups: 允许打开新窗口或弹出窗口。allow-popups-to-escape-sandbox: 允许新打开的窗口不受当前沙箱限制。allow-same-origin: 允许沙盒中的内容维持其原始来源(⚠️ 与allow-scripts并用有高安全风险)。allow-scripts: 允许在沙盒中执行脚本。allow-top-navigation: 允许沙盒内容将顶层窗口导航到新 URL
iframe 性能优化
iframe 天生会阻塞 window.load,defer 对 iframe 无效—— 因为 defer 只对 <script> 生效,iframe 是独立文档,浏览器必须等它(及里面所有资源)加载完才会触发主页面 load。
window.load 定义:要等主页面 + 所有子资源(iframe、图片、字体、样式表、脚本) 全部加载完成才触发。
iframe 本质:是一个独立的浏览器文档,浏览器把它当作 “必须完成的子资源”,所以:
- 只要页面里有
<iframe src="...">,不管它在不在视口、是否隐藏,都会立刻发起请求 - 主页面
load必须等这个 iframe 连同它内部所有 JS / 图片 / CSS 全部加载完才会触发
示例 原生 iframe 懒加载
懒加载 iframe 是优化页面首屏性能的重要手段,避免非可视区域的 iframe 加载阻塞资源。
触发时机由浏览器决定(接近视口)
<p>iframe 懒加载优化</p>
<div class="iframe-content">this this is iframe</div>
<iframe loading="lazy" src="iframe.html"></iframe>
示例 手动控制延迟加载 iframe(IntersectionObserver)
利用 IntersectionObserver ,每次交叉变化都会触发加载
<iframe id="lazyFrame" data-src="iframe.html"></iframe>
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const iframe = entry.target;
iframe.src = iframe.dataset.src;
}
});
});
observer.observe(document.getElementById("lazyFrame"));
示例 延迟加载 先占位 about:blank,再赋值 src
<iframe id="lazyFrame" data-src="iframe.html" src="about:blank"></iframe>
window.addEventListener("DOMContentLoaded", function () {
console.log("当前时间", new Date().toLocaleString());
console.log("加载完成");
const iframe = document.getElementById("lazyFrame");
iframe.src = iframe.dataset.src;
});
DOMContentLoaded 早触发,只等 DOM 解析完成;load 晚触发,要等页面所有资源(DOM、样式、脚本、图片、iframe、音视频)全部加载完毕。
页面从上到下解析、加载的完整时序:
- 开始解析 HTML,构建 DOM 树
- 遇到样式表,构建 CSSOM 树
- DOM + CSSOM 合成 渲染树
- DOMContentLoaded:DOM 树构建完成 立即触发(不等图片、iframe、大资源)
- 继续加载图片、视频、iframe、字体等外部子资源
- load:页面所有资源全部加载、解析、渲染完毕 才触发
<iframe id="lazyFrame" data-src="iframe.html" src="about:blank"></iframe>
window.addEventListener("load", function () {
console.log("当前时间", new Date().toLocaleString());
console.log("加载完成");
const iframe = document.getElementById("lazyFrame");
iframe.src = iframe.dataset.src;
});
示例 延迟创建 iframe
window.addEventListener("load", function () {
console.log("当前时间", new Date().toLocaleString());
console.log("加载完成");
const iframe = document.createElement("iframe");
iframe.id = "lazyFrame";
iframe.src = "iframe.html";
iframe.style.display = "none";
iframe.onload = function () {
console.log("iframe加载完成");
iframe.style.display = "block";
};
document.body.appendChild(iframe);
});
示例 路由同步
模拟 单页应用里 iframe 路由与父页面路由联动(通过 postMessage)
- 利用
history.pushState无刷新 - 利用
popstate事件
主页面(父页面)
<body>
<h1>Parent Page</h1>
<iframe id="childFrame" src="child.html" width="800" height="400"></iframe>
<div>当前父路由:<span id="parentRoute"></span></div>
<script>
const iframe = document.getElementById("childFrame");
let isSyncing = false; // 防止循环同步
// 更新父页面显示
function updateParentDisplay(path) {
document.getElementById("parentRoute").innerText = path;
}
// 父路由变化时通知 iframe
function notifyIframe(path) {
if (isSyncing) return;
if (iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
type: "ROUTE_CHANGE",
path: path,
},
"*",
); // 生产环境请指定目标 origin
}
}
// 监听父页面自身路由变化(popstate 或 pushState)
function onParentRouteChange() {
const path = window.location.pathname; // 或使用 hash
updateParentDisplay(path);
notifyIframe(path);
}
// 监听 iframe 发来的路由变化
window.addEventListener("message", (event) => {
// 生产环境请验证 event.origin
if (event.data && event.data.type === "IFRAME_ROUTE_CHANGE") {
isSyncing = true;
const newPath = event.data.path;
// 更新父页面 URL,不触发 popstate(使用 replaceState 或 pushState)
if (window.location.pathname !== newPath) {
history.pushState(null, "", newPath);
updateParentDisplay(newPath);
}
isSyncing = false;
}
});
// 监听 popstate(浏览器前进后退)
window.addEventListener("popstate", () => {
onParentRouteChange();
});
// 初始化:首次加载同步
iframe.onload = () => {
onParentRouteChange();
};
</script>
</body>
iframe 子页面
<body>
<h2>Child Iframe</h2>
<div>当前子路由:<span id="childRoute"></span></div>
<button onclick="navigate('/page1')">Go to Page 1</button>
<button onclick="navigate('/page2')">Go to Page 2</button>
<script>
let isSyncing = false;
// 更新 iframe 内部显示
function updateChildDisplay(path) {
document.getElementById("childRoute").innerText = path;
}
// 主动改变 iframe 路由(并通知父页面)
function navigate(path) {
if (isSyncing) return;
if (window.location.pathname !== path) {
history.pushState(null, "", path);
updateChildDisplay(path);
notifyParent(path);
}
}
// 通知父页面路由变化
function notifyParent(path) {
window.parent.postMessage(
{
type: "IFRAME_ROUTE_CHANGE",
path: path,
},
"*",
); // 生产环境指定父页面 origin
}
// 监听父页面发来的路由变化
window.addEventListener("message", (event) => {
// 生产环境请验证 event.origin
if (event.data && event.data.type === "ROUTE_CHANGE") {
const newPath = event.data.path;
if (window.location.pathname !== newPath) {
isSyncing = true;
history.pushState(null, "", newPath);
updateChildDisplay(newPath);
isSyncing = false;
}
}
});
// 监听 iframe 内自身路由变化(popstate)
window.addEventListener("popstate", () => {
const path = window.location.pathname;
updateChildDisplay(path);
notifyParent(path);
});
// 初始化显示
updateChildDisplay(window.location.pathname);
</script>
</body>
回顾 IntersectionObserver API
IntersectionObserver(交叉观察器)用于异步检测目标元素与根容器(视口 / 指定父容器)是否发生可视区域交叉,常用来实现懒加载、触底加载、曝光埋点、动画触发,也是解决 iframe 提前加载阻塞 load 的优选方案。
const observer = new IntersectionObserver(callback, options);
interface IntersectionObserver {
constructor(IntersectionObserverCallback callback, optional IntersectionObserverInit options = {});
readonly attribute (Element or Document)? root;
readonly attribute DOMString rootMargin;
readonly attribute DOMString scrollMargin;
readonly attribute FrozenArray<double> thresholds;
undefined observe(Element target);
undefined unobserve(Element target);
undefined disconnect();
sequence<IntersectionObserverEntry> takeRecords();
};
第二个参数 option
interface IntersectionObserverInit {
root?: Element | Document | null;
rootMargin?: string;
scrollMargin?: string;
threshold?: number | number[];
}
阈值(threshold)的工作原理
// 默认 threshold: [0]
// 回调触发时机:交叉比例跨越 0 时(进入 / 离开)
new IntersectionObserver(callback, {
threshold: [0, 0.5, 1.0]
});
// 回调触发时机:
// 交叉比例跨越 0 时 → 进入/离开视口
// 交叉比例跨越 0.5 时 → 可见面积达到 50%
// 交叉比例跨越 1.0 时 → 完全可见
| threshold | 触发时机 |
|---|---|
[0](默认) | 进入视口 / 离开视口 |
[0.5] | 可见面积达到 50% / 从 50% 以上降到 50% 以下 |
[1.0] | 完全可见 / 从完全可见变为部分不可见 |
[0, 0.25, 0.5, 0.75, 1.0] | 每跨越一个 25% 边界都触发 |
每个阈值都是双向的——从下往上跨越和从上往下跨越都会触发回调。
回调函数 callback(entries, observer)
entries:数组,状态发生变化的元素集合(批量触发)observer:当前观察器实例
interface IntersectionObserverEntry {
constructor(IntersectionObserverEntryInit intersectionObserverEntryInit);
readonly attribute DOMHighResTimeStamp time;
readonly attribute DOMRectReadOnly? rootBounds;
readonly attribute DOMRectReadOnly boundingClientRect;
readonly attribute DOMRectReadOnly intersectionRect;
readonly attribute boolean isIntersecting;
readonly attribute double intersectionRatio;
readonly attribute Element target;
};
entry.target // 被监听的 DOM 元素
entry.isIntersecting // 布尔值:元素是否进入可视区(最常用)
entry.intersectionRatio // 交叉比例 0 ~ 1(0=完全不可见,1=完全可见)
entry.boundingClientRect // 目标元素位置尺寸
entry.rootBounds // 根容器位置尺寸
entry.intersectionRect // 交叉区域尺寸