iframe 有哪些常见的应用场景?
iframe 用得最多的就两件事:嵌入第三方内容(视频、地图、广告),和隔离不信任的代码。剩下的场景要么是锦上添花,要么是被逼无奈。
嵌入第三方内容,这个没得选。YouTube 给你 iframe 嵌入代码,Google Maps 给你 iframe 嵌入代码——你不会去重新实现一个视频播放器或地图引擎。唯一能做的是优化加载:loading="lazy" 懒加载,title 属性做无障碍,别让 iframe 阻塞首屏渲染。实测一个 YouTube iframe 能增加 200-500KB 的首屏加载量,不懒加载就是在拖慢页面。
隔离不可信代码是 iframe 的核心价值。广告、用户提交的 HTML、第三方登录——这些代码你不能让它们直接跑在你的页面上下文里。CSS 样式污染、JS 全局变量覆盖,出过事的团队太多了。iframe + sandbox + postMessage 是目前浏览器原生能提供的最可靠的隔离方案。
微前端里 iframe 是兜底方案,不是首选。qiankun、Module Federation 能搞定 90% 的场景,只有子应用技术栈完全不兼容、或者安全隔离要求极高时才上 iframe。代价很实际:路由同步要手写桥接、position: fixed 弹窗相对于 iframe 视口而非主页面、每个 iframe 都是独立浏览器上下文吃内存。一个页面 3 个以上 iframe,低端设备就能感知到卡顿。
什么时候不该用 iframe:需要 SEO 收录的内容(搜索引擎基本不索引 iframe 内容)、需要频繁通信的组件(postMessage 序列化有性能开销)、移动端复杂交互(触摸事件跨 iframe 传递有问题)。能用 Web Component 或动态 import 解决的,别上 iframe。
追问
iframe 和 Web Component 有什么区别?
| iframe | Web Component | |
|---|---|---|
| 隔离级别 | 完全隔离(独立文档) | Shadow DOM 样式隔离 |
| 通信方式 | postMessage | 属性/事件 |
| 性能开销 | 高(独立上下文) | 低(同一文档) |
| SEO 可见 | 不可见 | 可见 |
| 适用场景 | 跨域嵌入 | 组件封装 |
一句话:跨域或强隔离用 iframe,同页面组件封装用 Web Component。
sandbox 属性有哪些常见的安全陷阱?
sandbox 默认禁止一切,你需要显式开放权限。最常见的陷阱是同时开 allow-scripts 和 allow-same-origin——这等于没隔离,脚本可以 frameElement.removeAttribute('sandbox') 直接移除限制。安全的做法是只开 allow-scripts 不开 allow-same-origin。另一个容易被忽略的是 allow-popups,如果不限制,iframe 内的链接可以弹出新窗口执行钓鱼攻击。
postMessage 通信出过什么安全问题?
必须校验 event.origin,targetOrigin 绝对不要写 *。真实事故案例:某 SaaS 产品的第三方插件通过 postMessage 向父页面发送伪造的用户操作数据,因为接收端只校验了 event.data.type 没校验 event.origin,导致用户权限被越权提升。
微前端里用 iframe 的实际痛点是什么?
路由同步是老大难——浏览器前进后退、URL hash 变化都要手动桥接,稍有不慎就不同步。弹窗定位是另一个坑,position: fixed 在 iframe 里是相对于 iframe 视口,不是主页面,导致弹窗遮挡位置完全错乱。还有 cookie 和 localStorage 的隔离问题,子应用登录态拿不到,得通过 postMessage 中转 token。
移动端 iframe 有什么坑?
iOS Safari 对 iframe 的滚动行为处理和其他浏览器不同,经常出现双滚动条或滚动穿透。触摸事件无法从主页面传递到 iframe 内部,意味着你自己写的滑动手势在 iframe 区域会失效。大部分团队最终选择移动端不用 iframe,改用原生组件或 WebView 方案。
写段代码
html<iframe src="https://example.com/widget" sandbox="allow-scripts" loading="lazy" title="第三方组件"> </iframe> <script> window.addEventListener('message', (e) => { if (e.origin !== 'https://example.com') return; if (e.data.type === 'ready') { e.source.postMessage({ type: 'init', userId: 123 }, e.origin); } }); </script>