Frame 与 Window
弹窗与 window 方法
弹窗
弹窗自古以来就存在。最初的想法是,在不关闭主窗口的情况下显示其他内容。(也可以通过 fetch 动态请求并将其渲染在动态生成的 div 中也具有同样效果)
弹窗语法:
window.open(url, name, params)
url:要在新窗口中加载的 URL。name:新窗口的名称,每个窗口都有一个全局的window.name。- 当给定的
name已存在窗口,则在已存在窗口打开新标签页。 - 不存在则打开新窗口打开标签页。
- 当给定的
params:新窗口的配置字符串。对于配置不合理的参数,浏览器会自行修复:
- 窗口参数会使用默认值
no。 - 没有
width/height,那么新窗口的大小将与上次打开的窗口大小相同。 - 没有
left/top,那么浏览器会尝试在最后打开的窗口附近打开一个新窗口。
- 窗口参数会使用默认值
| 设置项 | 类型 | 说明 | 限制/注意 |
|---|---|---|---|
| 位置 | |||
left | 数字 | 窗口左上角的 X 坐标 | 不能将窗口置于屏幕外 |
top | 数字 | 窗口左上角的 Y 坐标 | 不能将窗口置于屏幕外 |
width | 数字 | 窗口宽度 | 有最小值限制,不可创建不可见窗口 |
height | 数字 | 窗口高度 | 有最小值限制,不可创建不可见窗口 |
| 窗口功能 | |||
menubar | yes/no | 显示/隐藏浏览器菜单栏 | — |
toolbar | yes/no | 显示/隐藏导航栏(后退、前进、刷新等) | — |
location | yes/no | 显示/隐藏 URL 地址栏 | Firefox 和 IE 不允许默认隐藏 |
status | yes/no | 显示/隐藏状态栏 | 大多数浏览器强制显示 |
resizable | yes/no | 允许/禁止调整窗口大小 | 不建议使用 |
scrollbars | yes/no | 允许/禁止显示滚动条 | 不建议使用 |
更多内容:MDN window.open
移动端限制:移动设备无法同时显示多个窗口,但仍有部分场景在使用(如 OAuth 授权)。
弹窗优势:
- 是一个独立的窗口,具有自己的独立 JavaScript 环境。可以放心打开第三方链接。
- 打开弹窗非常容易。
- 弹窗可以导航(修改 URL),并将消息发送到 opener 窗口(即打开弹窗的窗口)。
⚠️ 安全限制:非用户手动触发的弹窗将会被浏览器拦截,并询问用户的同意/拒绝。
// ❌ 弹窗被阻止
window.open('https://javascript.info');
// ✅ 弹窗被允许
button.onclick = () => {
window.open('https://javascript.info');
};
延迟限制:
- Firefox 中接受 2000ms 及以内的延迟允许打开弹窗,超过则默认不允许(用户同意后即可打开)。
- Chrome 则 3000ms 也能够打开。
弹窗与 opener 窗口双向访问
只有在窗口是同源的时,窗口才能自由访问彼此的内容。两个不同站点是不能访问彼此内容的。
opener 窗口访问弹窗
window.open 方法会返回对新窗口的引用:
let newWin = window.open("about:blank", "hello", "width=200,height=200");
newWin.document.write("Hello, world!");
弹窗访问 opener 窗口
弹窗使用 window.opener 来访问 opener 窗口。除了弹窗之外,对其他所有窗口来说,window.opener 均为 null。
let newWin = window.open("about:blank", "hello", "width=200,height=200");
newWin.document.write(
"<script>window.opener.document.body.innerHTML = 'Test'<\/script>"
);
关闭弹窗
如果 window 不是通过 window.open() 创建的,那么大多数浏览器都会忽略 window.close()。因此,close() 只对弹窗起作用。
关闭窗口语法:
// 只对弹窗起作用
win.close()
检测窗口关闭状态:
win.closed // true 表示已关闭
移动和调整大小
- 为了防止滥用,浏览器通常会阻止这些方法。仅在我们打开的、没有其他选项卡的弹窗中能够可靠地工作。
- JavaScript 无法最小化或者最大化一个窗口。
-
win.moveBy(x, y)
相对于当前位置移动窗口:向右x像素,向下y像素。允许负值(向左/向上移动)。win.moveBy(100, 50); // 向右 100px,向下 50px win.moveBy(-50, -30); // 向左 50px,向上 30px -
win.moveTo(x, y)
将窗口移动到屏幕绝对坐标(x, y)处。win.moveTo(100, 100); // 移动到 (100, 100) win.moveTo(0, 0); // 移动到左上角 -
win.resizeBy(width, height)
相对于当前大小调整窗口:宽度增加width,高度增加height。允许负值(缩小)。win.resizeBy(200, 100); // 增大 win.resizeBy(-100, -50); // 缩小 -
win.resizeTo(width, height)
将窗口调整为指定的绝对大小。win.resizeTo(800, 600); -
window.onresize
窗口大小改变时触发的事件处理器。window.addEventListener('resize', () => { console.log(`新尺寸: ${window.innerWidth}x${window.innerHeight}`); });
滚动窗口
-
win.scrollBy(x, y)
相对于当前滚动位置滚动。window.scrollBy(0, 300); // 向下滚动 300px window.scrollBy({ top: 300, behavior: 'smooth' }); // 平滑滚动 -
win.scrollTo(x, y)
滚动到文档中的绝对坐标。window.scrollTo(0, 0); // 回到顶部 -
elem.scrollIntoView(alignToTop)
滚动使元素可见。element.scrollIntoView({ behavior: 'smooth', block: 'center' }); -
window.onscroll
滚动时触发(建议防抖优化)。window.addEventListener('scroll', debounce(() => { console.log('滚动中...'); }, 100));
弹窗聚焦/失焦
-
window.focus()
尝试使窗口获得焦点(将窗口置于最前)。⚠️ 受浏览器严格限制:移动端忽略、需用户交互后执行、选项卡式弹窗无效。
-
window.blur()
使窗口失去焦点。❌ 几乎被所有浏览器禁用(防止恶意后台锁定)。
-
window.onfocus/window.onblur
窗口获得/失去焦点时触发。window.addEventListener('focus', () => resume()); window.addEventListener('blur', () => pause()); -
document.hasFocus()
检查当前文档是否具有焦点。 -
document.visibilityState&visibilitychange(✅ 推荐)
更可靠的页面可见性检测(能识别选项卡切换):document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { resumeAnimations(); } else { pauseAnimations(); } });
实际应用场景
| 场景 | 推荐方案 |
|---|---|
| 用户活跃度跟踪 | visibilitychange |
| 视频/动画控制 | document.visibilityState |
| 实时通信状态 | onfocus + onblur |
浏览器限制总结
| 方法 | 限制原因 |
|---|---|
focus() | 防止强制弹窗劫持 |
blur() | 防止强制后台运行 |
| 自动聚焦 | 弹窗拦截器保护用户 |
跨窗口通信
同源策略
两个 URL 具有相同的协议、域名、端口,则称它们是“同源”的。
非同源示例:
http://www.site.com(子域不同)http://site.org(顶级域不同)https://site.com(协议不同)http://site.com:8080(端口不同)
对弹窗的权限:
- 同源:opener 对弹窗具有全部权限(读写 DOM、调用方法等)。
- 非同源:仅允许修改
location.href,但不可读取location内容。
let newWin = window.open('https://azusatea.top');
console.log(newWin.location); // Location about:blank(无法读取真实 URL)
setTimeout(() => {
newWin.location.href = 'https://fanyi.baidu.com/'; // ✅ 允许修改
}, 1000);
iframe
<iframe> 具有属于自己的 window 和 document。
iframe.contentWindow→ 获取 iframe 的windowiframe.contentDocument→ 获取 iframe 的document(等价于iframe.contentWindow.document)
同源规则同样适用:
- 同源:可完全访问和修改内容。
- 非同源:拒绝访问(
location例外,只允许修改)。
注意:创建 iframe 后,它立即拥有一个空文档,但不同于最终加载的文档。应使用
iframe.onload确保文档加载完成后再操作。
<iframe src="/" id="iframe"></iframe>
<script>
iframe.onload = () => {
console.log("iframe 文档已加载");
iframe.contentDocument.body.style.background = "lightblue";
};
</script>
iframe 集合
通过 window.frames 访问:
window.frames[0]:第一个 iframe 的windowwindow.frames.iframeName:name="iframeName"的 iframe 的window
<iframe src="/" name="win" id="iframe"></iframe>
<script>
console.log(iframe.contentWindow === frames[0]); // true
console.log(iframe.contentWindow === frames.win); // true
</script>
iframe 层级关系
window.frames:子窗口集合(嵌套 iframe)window.parent:父窗口引用window.top:最顶级窗口引用window === top可判断当前是否在 iframe 中
沙盒 iframe
通过 sandbox 属性严格限制 iframe 能力:
| 属性值 | 作用 | 默认 |
|---|---|---|
allow-same-origin | 允许同源(移除跨域限制) | ❌ |
allow-top-navigation | 允许修改 parent.location | ❌ |
allow-forms | 允许提交表单 | ❌ |
allow-scripts | 允许执行 JS | ❌ |
allow-popups | 允许 window.open | ❌ |
sandbox(空) | 完全禁止一切 | — |
<!-- 最安全:仅静态展示 -->
<iframe src="ad.html" sandbox></iframe>
<!-- 可信组件:允许脚本 + 同源 -->
<iframe src="widget.html" sandbox="allow-scripts allow-same- origin"></iframe>
⚠️ 如果 iframe 来自其他源,即使设置
allow-same-origin也无法放宽同源策略。
跨窗口通信:postMessage
发送方
targetWindow.postMessage(data, targetOrigin);
data:要发送的数据(支持对象,IE 需JSON.stringify)targetOrigin:目标源(如'https://example.com'),'*'表示不限制(敏感数据慎用)
let win = iframe.contentWindow;
win.postMessage({ user: 'John' }, 'https://trusted.com');
接收方
window.addEventListener('message', (event) => {
// 1. 验证来源(安全必需!)
if (event.origin !== 'https://trusted.com') return;
// 2. 处理数据
console.log('收到:', event.data);
// 3. 回传消息
event.source.postMessage('OK', event.origin);
});
安全要点
| 措施 | 说明 |
|---|---|
✅ 验证 event.origin | 防止伪造消息 |
✅ 指定 targetOrigin | 避免使用 * |
✅ 验证 event.data | 防注入攻击 |
✅ 使用 event.source 回传 | 确保回复正确窗口 |
点击劫持攻击(Clickjacking)
原理
- 放置一个无害链接。
- 在其上方用
z-index覆盖一个透明 iframe,src指向银行/社交网站。 - 用户点击“无害链接”,实际点击了 iframe 中的“删除账户”按钮。
仅针对点击事件,键盘事件无效。
防御措施
1. 阻止顶级导航(旧方法)
window.onbeforeunload = () => false;
用户点击 iframe 时会弹出确认框,通常会选择“取消”,从而阻止跳转。
❌ 已过时,现代浏览器限制此行为。
2. 沙箱 iframe(开发者控制)
<iframe sandbox="allow-scripts allow-forms" src="..."></iframe>
未设置
allow-top-navigation,因此 iframe 无法修改top.location。
3. X-Frame-Options(服务端控制)
HTTP 响应头,三种值:
DENY:禁止任何 frame 嵌入SAMEORIGIN:仅允许同源 frame 嵌入ALLOW-FROM uri:仅允许指定域嵌入(已废弃)
X-Frame-Options: DENY
4. 显示禁用(前端防护)
用全屏 div 覆盖页面,仅当 window === top 时移除:
<div id="protector">请直接访问本网站</div>
<script>
if (window === top) {
protector.remove();
}
</script>
5. SameSite Cookie(服务端防护)
Set-Cookie: auth=secret; SameSite=Lax
通过 iframe 加载的页面不会携带
SameSiteCookie,从而无法执行敏感操作。
总结
| 防御层级 | 措施 |
|---|---|
| 服务端 | X-Frame-Options、Content-Security-Policy: frame-ancestors、SameSite Cookie |
| 前端 | 检测 window !== top 并遮罩、沙箱 iframe |
| 用户 | 保持警惕,避免点击可疑链接 |
✅ 最佳实践:服务端设置
X-Frame-Options: DENY或CSP frame-ancestors 'self',前端辅以遮罩检测。