Iframe:用法全面梳理

一、Iframe 核心能力总览

能力分类 具体用法 难度 使用频率
🌐 基础嵌入 访问外部网站 🔥🔥🔥🔥🔥
🔄 导航控制 前进/后退/刷新 🔥🔥🔥🔥
📨 双向通信 postMessage ⭐⭐⭐ 🔥🔥🔥🔥🔥
📏 高度自适应 动态调整 iframe 高度 ⭐⭐ 🔥🔥🔥🔥
🎨 样式隔离 沙箱/权限控制 ⭐⭐ 🔥🔥🔥
🔐 安全通信 域名校验 + Token 传递 ⭐⭐⭐ 🔥🔥🔥🔥
🧪 本地调试 localhost 嵌套 iframe 🔥🔥🔥

二、六大用法详细拆解

📌 用法1:基础嵌入(最简单)

场景:直接展示百度、Google 等外部网站

特性 说明
目标URL https://www.baidu.comhttps://www.google.com
本地调试 http://localhost:5173http://localhost:5173/iframe.html
核心属性 srcframeborderallowfullscreen
复制代码
<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 类型定义 ❌ 无类型的任意消息传递