一、Iframe 核心能力总览
| 能力分类 |
具体用法 |
难度 |
使用频率 |
| 🌐 基础嵌入 |
访问外部网站 |
⭐ |
🔥🔥🔥🔥🔥 |
| 🔄 导航控制 |
前进/后退/刷新 |
⭐ |
🔥🔥🔥🔥 |
| 📨 双向通信 |
postMessage |
⭐⭐⭐ |
🔥🔥🔥🔥🔥 |
| 📏 高度自适应 |
动态调整 iframe 高度 |
⭐⭐ |
🔥🔥🔥🔥 |
| 🎨 样式隔离 |
沙箱/权限控制 |
⭐⭐ |
🔥🔥🔥 |
| 🔐 安全通信 |
域名校验 + Token 传递 |
⭐⭐⭐ |
🔥🔥🔥🔥 |
| 🧪 本地调试 |
localhost 嵌套 iframe |
⭐ |
🔥🔥🔥 |
二、六大用法详细拆解
📌 用法1:基础嵌入(最简单)
场景:直接展示百度、Google 等外部网站
| 特性 |
说明 |
| 目标URL |
https://www.baidu.com、https://www.google.com |
| 本地调试 |
http://localhost:5173、http://localhost:5173/iframe.html |
| 核心属性 |
src、frameborder、allowfullscreen |
<template>
<iframe :src="targetUrl" class="iframe-box" frameborder="0" allowfullscreen></iframe>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const targetUrl = ref('https://www.baidu.com')
</script>
<style scoped>
.iframe-box {
width: 100%;
height: 600px;
border: 2px solid #42b983;
border-radius: 8px;
}
</style>
📌 用法2:导航控制(前进/后退/刷新)
| 操作 |
API |
说明 |
| 后退 |
iframeRef.contentWindow.history.back() |
模拟浏览器后退 |
| 前进 |
iframeRef.contentWindow.history.forward() |
模拟浏览器前进 |
| 刷新 |
iframeRef.contentWindow.location.reload() |
重新加载页面 |
<script setup lang="ts">
import { ref } from 'vue'
const iframeRef = ref<HTMLIFrameElement>()
const goBack = () => iframeRef.value?.contentWindow?.history.back()
const goForward = () => iframeRef.value?.contentWindow?.history.forward()
const reload = () => iframeRef.value?.contentWindow?.location.reload()
// 动态切换 URL
const changeUrl = (url: string) => {
let u = url.trim()
if (!u.startsWith('http')) u = 'https://' + u
iframeRef.value!.src = u
}
</script>
<template>
<iframe ref="iframeRef" :src="targetUrl" class="iframe-box"></iframe>
<div class="controls">
<input v-model="urlInput" placeholder="输入网址..." />
<button @click="changeUrl(urlInput)">访问</button>
<button @click="goBack">+后退</button>
<button @click="goForward">➡ 前进</button>
<button @click="reload">🔄 刷新</button>
</div>
</template>
📌 用法3:postMessage 双向通信(⭐推荐)
场景:父组件 ↔ iframe 传递数据(Token、表单、主题等)
| 方向 |
发送方 |
接收方 |
示例数据 |
| 父 → 子 |
Vue 组件 |
iframe 内部 |
{ type: 'THEME', theme: 'dark' } |
| 子 → 父 |
iframe 内部 |
Vue 组件 |
{ type: 'FROM_IFRAME', message: 'Hello' } |
🔒 安全校验(必须做)
| 措施 |
✅ 正确 |
❌ 错误 |
| 校验来源 |
if (event.origin !== 'http://localhost:5173') return |
不校验直接用 |
| 目标域名 |
postMessage(data, 'https://yourdomain.com') |
postMessage(data, '*') |
| 数据验证 |
接收后校验结构和类型 |
直接使用 |
👨💻 完整代码
父组件(Vue 3)
<template>
<div class="iframe-wrapper">
<h2>🌐 Iframe 双向通信</h2>
<input v-model="currentUrl" @keyup.enter="loadUrl" placeholder="输入网址..." class="url-input" />
<button @click="loadUrl" class="btn-primary">访问</button>
<!-- 通信日志 -->
<div class="message-log">
<h3>📨 通信日志</h3>
<div v-for="(msg, idx) in messages" :key="idx" class="message-item">
<span class="tag">{{ msg.type }}</span>
<span>{{ msg.data }}</span>
<span class="time">{{ msg.time }}</span>
</div>
</div>
<iframe
ref="iframeRef"
:src="currentUrl"
class="iframe-box"
@load="onIframeLoad"
></iframe>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const currentUrl = ref('http://localhost:5173/iframe.html')
const iframeRef = ref<HTMLIFrameElement>()
const messages = ref<Array<{ type: string; data: string; time: string }>>([])
// ✅ 接收 iframe 消息
const onMessage = (event: MessageEvent) => {
// 🔒 安全校验
if (event.origin !== 'http://localhost:5173') {
console.warn('⚠️ 来源不安全,忽略消息')
return
}
const data = event.data
messages.value.push({
type: data.type || 'MSG',
data: JSON.stringify(data),
time: new Date().toLocaleTimeString()
})
}
// ✅ 发送消息给 iframe
const sendToIframe = (data: any) => {
iframeRef.value?.contentWindow?.postMessage(data, 'http://localhost:5173')
}
const loadUrl = () => {
let url = currentUrl.value.trim()
if (!url.startsWith('http')) url = 'http://' + url
currentUrl.value = url
sendToIframe({ type: 'NAVIGATE', url })
}
const onIframeLoad = () => {
sendToIframe({ type: 'INIT', message: 'Parent ready!' })
}
onMounted(() => window.addEventListener('message', onMessage))
onUnmounted(() => window.removeEventListener('message', onMessage))
</script>
<style scoped>
.iframe-box { width: 100%; height: 500px; border: 2px solid #42b983; border-radius: 8px; }
.url-input { flex: 1; min-width: 300px; padding: 10px 15px; border: 2px solid #ddd; border-radius: 6px; }
.btn-primary { padding: 10px 20px; background: #42b983; color: white; border: none; border-radius: 6px; cursor: pointer; }
.message-log { margin-top: 20px; max-height: 200px; overflow-y: auto; }
.message-item { padding: 8px; background: #f5f5f5; margin: 5px 0; border-radius: 4px; }
.tag { background: #42b983; color: white; padding: 2px 8px; border-radius: 4px; margin-right: 10px; }
</style>
子页面(iframe.html)
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial; padding: 20px; }
.message-box { padding: 10px; background: #e8f5e9; margin: 10px 0; border-radius: 4px; }
button { margin: 10px; padding: 10px 20px; cursor: pointer; background: #42b983; color: white; border: none; border-radius: 6px; }
</style>
</head>
<body>
<h2>📌 Iframe 内部页面</h2>
<div id="messages"></div>
<button onclick="sendToParent()">📤 发送消息给父组件</button>
<script>
// 📥 接收父组件消息
window.addEventListener('message', (event) => {
// 🔒 安全校验
if (event.origin !== 'http://localhost:5173') {
console.warn('⚠️ 来源不安全,忽略消息')
return
}
const data = event.data
console.log('📩 收到:', data)
const div = document.createElement('div')
div.className = 'message-box'
div.innerHTML = `<strong>类型:</strong> ${data.type} | <strong>数据:</strong> ${JSON.stringify(data)}`
document.getElementById('messages').appendChild(div)
})
// 📤 发送消息给父组件
function sendToParent() {
if (window.parent) {
window.parent.postMessage({
type: 'FROM_IFRAME',
message: 'Hello from Iframe!',
time: new Date().toLocaleString()
}, 'http://localhost:5173')
}
}
// 初始化主动发送
setTimeout(() => sendToParent(), 500)
</script>
</body>
</html>
📌 用法4:高度自适应
| 方法 |
实现方式 |
适用场景 |
| postMessage 传高度 |
iframe 内部 postMessage({ type: 'RESIZE', height: 800 }) |
✅ 推荐,跨域可用 |
scrolling="no" |
禁用滚动条 |
简单场景 |
CSS height: 100vh |
占满视口 |
固定高度 |
<script setup lang="ts">
const onMessage = (event: MessageEvent) => {
if (event.data.type === 'RESIZE') {
iframeHeight.value = event.data.height
}
}
</script>
<template>
<iframe
:src="currentUrl"
:style="{ height: iframeHeight + 'px' }"
@load="onIframeLoad"
></iframe>
</template>
iframe 内部发送高度:
// iframe 内部
function reportHeight() {
window.parent.postMessage({ type: 'RESIZE', height: document.body.scrollHeight }, '*')
}
window.onload = reportHeight
window.onresize = reportHeight
📌 用法5:沙箱安全控制
| sandbox 属性值 |
效果 |
""(空) |
默认,允许大部分操作 |
"allow-same-origin" |
允许同源访问 |
"allow-scripts" |
允许执行 JS |
"allow-forms" |
允许表单提交 |
"allow-top-navigation" |
允许顶部导航(不会被沙箱限制) |
"allow-popups" |
允许弹窗 |
<!-- 🔒 严格沙箱:只允许同源 + 脚本,禁止表单/弹窗/导航 -->
<iframe src="https://www.baidu.com" sandbox="allow-same-origin allow-scripts"></iframe>
<!-- 🔓 宽松沙箱:允许所有 -->
<iframe src="https://www.google.com" sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"></iframe>
📌 用法6:TypeScript 类型定义(推荐)
// types/postmessage.ts
export interface ParentMessage {
type: string
timestamp: number
[key: string]: any
}
export interface IframeMessage {
type: 'FROM_IFRAME' | 'INIT_DONE' | 'RESIZE' | 'THEME'
payload?: any
message?: string
time?: string
height?: number
}
// 使用
const sendMessageToIframe = (data: ParentMessage) => {
iframeRef.value?.contentWindow?.postMessage(data, 'http://localhost:5173')
}
三、常见应用场景对照表
| 场景 |
通信方式 |
数据示例 |
安全措施 |
| 🔑 传递用户 Token |
postMessage |
{ token: 'xxx', userId: 123 } |
校验 origin + HTTPS |
| 📏 iframe 高度自适应 |
postMessage |
{ type: 'RESIZE', height: 500 } |
校验 origin |
| 🎨 主题切换 |
postMessage |
{ type: 'THEME', theme: 'dark' } |
校验 origin |
| 📋 表单数据回传 |
postMessage |
{ type: 'FORM_DATA', data: {...} } |
校验 origin + 数据验证 |
| 🔄 同步滚动 |
postMessage |
{ type: 'SCROLL', position: 300 } |
校验 origin |
四、完整通信流程图
`┌─────────────────────┐ postMessage ┌─────────────────────┐
│ Vue 3 父组件 │ ────────────────────────────▶ │ iframe │
│ │ { type: 'THEME', theme: 'dark' } │ │
│ │ ◀──────────────────────────── │ │
│ │ { type: 'FROM_IFRAME', │ │
│ │ message: 'Hello' } │ │
└─────────────────────┘ └─────────────────────┘
│ │
│ 🔒 校验 event.origin │
│ 🔒 postMessage(data, 'http://localhost:5173') │
│ 🔒 验证数据结构 │
`
五、参考资料中的 URL 映射
| 资料链接 |
用途 |
在代码中的角色 |
https://www.baidu.com |
基础嵌入示例 |
targetUrl 默认值 |
https://www.google.com |
跨域嵌入示例 |
动态切换 URL |
http://localhost:5173 |
Vue 3 开发服务器 |
postMessage 目标 origin |
http://localhost:5173/iframe.html |
iframe 内部页面 |
src 绑定值 |
https://yourdomain.com |
生产环境域名 |
postMessage 目标域名(替换 *) |
六、最佳实践 Checklist
| ✅ 必须做 |
❌ 禁止做 |
✅ 校验 event.origin |
❌ 使用 postMessage(data, '*') 生产环境 |
| ✅ 接收后验证数据结构 |
❌ 盲目信任 iframe 发来的数据 |
✅ 使用 sandbox 限制权限 |
❌ 空 sandbox 嵌入不可信网站 |
| ✅ HTTPS 环境下使用 |
❌ HTTP 页面嵌套 HTTPS iframe(混合内容) |
| ✅ TypeScript 类型定义 |
❌ 无类型的任意消息传递 |