小白前端别慌:iframe到底能干啥?3天搞懂用法+避坑指南(附实战技巧)

小白前端别慌:iframe到底能干啥?3天搞懂用法+避坑指南(附实战技巧)
- 小白前端别慌:iframe到底能干啥?3天搞懂用法+避坑指南(附实战技巧)
小白前端别慌:iframe到底能干啥?3天搞懂用法+避坑指南(附实战技巧)
说实话,我刚入行那会儿看到项目里满屏的iframe,心里直犯嘀咕:这玩意儿不是上古遗物吗?咋还这么多人用呢?后来踩了无数个坑才明白,iframe这货就像你家那把用了十年的螺丝刀——看着土,关键时刻还真离不开。
为啥老项目里总藏着一堆iframe
这事儿得从互联网的古早年代说起。那会儿前后端还没分家,PHP、JSP、ASP满天飞,一个页面可能就是几十个表格拼起来的。后来业务越来越复杂,团队开始搞"模块化"——说白了就是把页面切成一块一块的,你管你的用户中心,我管我的订单列表,最后找个"容器"把它们糊在一起。
iframe就是那个容器。
我见过最离谱的项目,一个后台管理系统里嵌了二十多个iframe,点左侧菜单就切换不同的iframe的src。当时负责维护的老哥跟我说:“这系统比我工龄还长,你敢动架构,业务部门就敢跟你拼命。”
所以别嫌弃这些"老古董",它们可能是某个时代的最优解。现在虽然微前端、模块联邦这些新概念很火,但iframe依然稳如老狗地躺在无数代码库里,等着你去维护。
iframe是啥玩意儿——浏览器里的"套娃神器"
官方定义说iframe是"内联框架",用来在当前页面里嵌另一个HTML文档。但我觉得这解释太正经了,不如叫它"浏览器套娃"——你在一个网页里打开另一个网页,那个网页里可能还套着第三个网页…
技术上讲,每个iframe都会创建一个独立的浏览上下文。啥意思?就是它有自己独立的window、document,甚至自己的JavaScript执行环境。这就像是给里面的内容开了个"独立包间",跟外面的页面互不干扰(当然,只是理论上)。
<!-- 最基础的用法,就像放张图片一样简单 --><iframesrc="https://www.example.com"></iframe><!-- 但实际项目中,你肯定得加一堆属性 --><iframesrc="https://www.example.com"width="800"height="600"frameborder="0"allowfullscreen></iframe>看到没?最简单的用法确实简单,但真要拿到生产环境用,没几个属性镇场子是不行的。
这标签到底能干点啥正经事
别觉得iframe只是用来嵌广告的(虽然早期确实很多广告用它)。正经场景多着呢:
第三方内容嵌入是最常见的。地图、视频播放器、支付页面、客服聊天窗口…这些你不可能自己写吧?直接拿别人现成的iframe嵌进来,省心省力。比如高德地图的嵌入代码:
<!-- 嵌个地图,几行代码搞定 --><iframewidth="600"height="400"frameborder="0"scrolling="no"src="https://uri.amap.com/marker?position=116.397428,39.90923&name=天安门&src=mypage&coordinate=gaode&callnative=0"></iframe>微前端架构里,iframe也是一种简单粗暴的实现方式。虽然业界现在更推崇single-spa、qiankun这些方案,但iframe的优势在于完全隔离——CSS不会冲突,JS不会互相污染,一个子应用挂了不会影响其他应用。对于需要快速整合的历史遗留系统,iframe往往是"最快能用"的方案。
预览功能也很实用。比如你要做个代码编辑器,需要实时预览效果,用iframe嵌个动态生成的页面,既安全又方便。或者后台管理系统里预览PDF、Office文档,很多也是用iframe加载第三方服务的预览页面。
沙箱环境更是iframe的杀手锏。后面会详细讲,但简单来说,如果你需要运行一些不可信的代码(比如用户提交的HTML片段),扔iframe里是最省事的隔离方案。
属性全家桶:src、sandbox、loading…每个都得盘明白
iframe的属性不少,但真正常用的就那几个。咱们一个个掰扯清楚。
src和srcdoc:从哪加载内容
src是最常用的,指向一个外部URL。但有时候你想直接塞HTML字符串进去,这时候srcdoc就派上用场了:
<!-- 传统方式,加载外部页面 --><iframesrc="https://example.com/page.html"></iframe><!-- srcdoc方式,直接写HTML --><iframesrcdoc="<html><body><h1>Hello World</h1></body></html>"></iframe>srcdoc在动态生成内容时特别好用。比如你要展示一段用户提交的HTML代码,但又怕有XSS风险,可以先过滤一遍,然后用srcdoc塞进iframe:
// 假设这是用户提交的代码,需要清理后展示const userHtml =`<div onclick="alert('xss')">点击我</div>`;// 简单的XSS过滤(实际项目要用更严谨的库)const sanitizedHtml = userHtml.replace(/on\w+="[^"]*"/g,'');// 动态创建iframe并写入内容const iframe = document.createElement('iframe'); iframe.srcdoc = sanitizedHtml; document.body.appendChild(iframe);sandbox:安全沙箱的配置手册
这是iframe最重要的安全属性,没有之一。它就像给iframe加了个"牢房",限制里面能干什么、不能干什么。
<!-- 最严格的限制,啥也不让干 --><iframesrc="https://example.com"sandbox></iframe><!-- 放开一些权限,允许提交表单和打开弹窗 --><iframesrc="https://example.com"sandbox="allow-forms allow-popups"></iframe><!-- 比较宽松但仍有底线 --><iframesrc="https://example.com"sandbox="allow-scripts allow-same-origin allow-forms"></iframe>sandbox的可选值有这些,每个都要掂量清楚再用:
allow-scripts:允许执行JavaScript。很多第三方页面需要这个,但也是最危险的。allow-same-origin:允许把iframe当成同源页面。注意,如果同时开了allow-scripts和allow-same-origin,iframe里的代码就能访问父页面的cookie,风险很大。allow-forms:允许提交表单。支付页面通常需要这个。allow-popups:允许打开新窗口(window.open)。allow-top-navigation:允许iframe改变父页面的地址(top.location)。这个要特别小心,恶意网站可能用它做钓鱼跳转。allow-pointer-lock:允许指针锁定(游戏常用)。allow-modals:允许模态对话框(alert、confirm等)。
一个常见的坑:你嵌了个第三方登录页面,发现点登录没反应。大概率是忘了加allow-forms和allow-scripts。但别一股脑全开,按需给权限才是正经做法。
loading:懒加载救性能
现代浏览器支持loading="lazy",让iframe像图片一样延迟加载:
<!-- 页面滚动到附近才加载,省流量省性能 --><iframesrc="https://example.com"loading="lazy"width="600"height="400"></iframe>但要注意,懒加载只在iframe初始时不在视口内才有效。如果你用js动态改src,这个属性就不起作用了。那时候得自己用Intersection Observer或者简单的scroll监听来实现。
// 自己实现懒加载,控制更精细const iframe = document.createElement('iframe'); iframe.dataset.src ='https://example.com';// 先存起来const observer =newIntersectionObserver((entries)=>{ entries.forEach(entry=>{if(entry.isIntersecting){// 进入视口了,真正加载 iframe.src = iframe.dataset.src; observer.unobserve(iframe);}});}); observer.observe(iframe); document.body.appendChild(iframe);其他常用属性
frameborder="0":去掉边框,现在更推荐用CSS的border:none。
allowfullscreen:允许全屏,嵌视频播放器必备。
name:给iframe起个名字,配合<a target="iframename">可以做导航,老派但好用。
referrerpolicy:控制referer头怎么发,隐私敏感场景有用。
安全沙箱怎么配才不被老板骂
安全这事儿,配松了怕出事,配严了怕功能跑不起来。这里说几个血泪教训。
绝对不要同时给allow-scripts和allow-top-navigation。恶意脚本可以直接把父页面跳转到钓鱼网站。如果业务需要跳转,考虑用postMessage让父页面来处理。
第三方内容一定要加sandbox。哪怕是你信任的供应商,也难保他们的页面被注入恶意代码。最小权限原则永远没错。
<!-- 比如嵌个支付页面,只需要表单提交和脚本执行 --><iframesrc="https://payment.provider.com/checkout"sandbox="allow-scripts allow-forms allow-same-origin"referrerpolicy="no-referrer"></iframe>敏感操作加confirm。如果iframe里的内容需要做一些危险操作(比如删除数据),最好让父页面弹个确认框,而不是完全信任iframe里的逻辑。
// 父页面监听消息,做二次确认 window.addEventListener('message',(event)=>{// 验证来源,后面会讲if(event.origin !=='https://trusted-domain.com')return;if(event.data.type ==='DELETE_REQUEST'){if(confirm('确定要删除吗?此操作不可恢复')){// 告诉iframe可以执行了 event.source.postMessage({type:'DELETE_CONFIRMED'}, event.origin);}}});CSP(内容安全策略)也要配合上。在HTTP头里设置Content-Security-Policy: frame-ancestors 'self' https://trusted.com,可以防止你的页面被恶意网站嵌到他们的iframe里(点击劫持攻击)。
跨域通信那点破事儿:postMessage真香但容易翻车
同源策略是前端安全的基础,但业务上总需要iframe和父页面传个话。postMessage就是干这个的,但用不好照样翻车。
基础用法很简单:
// 父页面向iframe发消息const iframe = document.getElementById('myIframe'); iframe.contentWindow.postMessage('你好啊','https://child-domain.com');// iframe向父页面发消息 window.parent.postMessage('收到','https://parent-domain.com');但这里有几个坑,我逐个给你标红:
坑1:不验证origin就是找死
// 错误示范:来者不拒,危险! window.addEventListener('message',(event)=>{ console.log(event.data);// 直接用了,万一来源不对呢?});// 正确做法:先验明正身 window.addEventListener('message',(event)=>{// 严格检查来源,白名单机制const allowedOrigins =['https://trusted1.com','https://trusted2.com'];if(!allowedOrigins.includes(event.origin)){ console.warn('可疑消息来源:', event.origin);return;}// 还要检查数据格式,别拿到个对象就瞎用if(typeof event.data !=='object'||!event.data.type){return;}// 现在可以安心处理了handleMessage(event.data);});坑2:targetOrigin别写*
// 偷懒写法,消息发给所有人,极其危险 iframe.contentWindow.postMessage(secretData,'*');// 正确写法,指名道姓发给谁 iframe.contentWindow.postMessage(secretData,'https://exact-domain.com');坑3:复杂数据要序列化
postMessage传递的是结构化克隆算法处理的数据,基本类型、对象、数组都没问题,但函数和DOM节点不行。如果要用函数回调,得用其他套路:
// 父页面const callbacks =newMap();let callbackId =0;functioncallChildMethod(method, params, callback){const id =++callbackId; callbacks.set(id, callback); iframe.contentWindow.postMessage({type:'CALL_METHOD', method, params,callbackId: id },'https://child-domain.com');} window.addEventListener('message',(event)=>{if(event.data.type ==='CALLBACK_RESULT'&& callbacks.has(event.data.callbackId)){ callbacks.get(event.data.callbackId)(event.data.result); callbacks.delete(event.data.callbackId);}});// iframe里 window.addEventListener('message',(event)=>{if(event.data.type ==='CALL_METHOD'){const result =someLocalMethod(event.data.params); event.source.postMessage({type:'CALLBACK_RESULT',callbackId: event.data.callbackId, result }, event.origin);}});坑4:内存泄漏
如果你频繁创建和销毁iframe,记得把postMessage的监听器也清理掉,不然闭包里的引用可能导致内存泄漏:
functioncreateIframe(){const iframe = document.createElement('iframe');constmessageHandler=(event)=>{// 处理消息...}; window.addEventListener('message', messageHandler);// 清理时要移除监听 iframe.addEventListener('load',()=>{// iframe加载新内容前的清理});return{destroy(){ window.removeEventListener('message', messageHandler); iframe.remove();}};}性能拖后腿?懒加载+按需渲染救你狗命
iframe是性能黑洞,这话一点不夸张。每个iframe都要独立解析HTML、加载资源、执行脚本,跟开一个新标签页差不多。如果一页嵌五六个iframe,低端机直接卡成PPT。
懒加载前面说过了,不在视口内的坚决不加载。但有些场景需要更精细的控制:
// 按需加载:点击tab时才加载对应iframeclassTabManager{constructor(){this.loadedTabs =newSet();this.iframes =newMap();}registerTab(tabId, url){// 先占个坑,不真加载const placeholder = document.createElement('div'); placeholder.dataset.url = url; placeholder.dataset.tabId = tabId;this.iframes.set(tabId, placeholder);return placeholder;}activateTab(tabId){const placeholder =this.iframes.get(tabId);if(!placeholder ||this.loadedTabs.has(tabId))return;// 真要显示了,换成iframeconst iframe = document.createElement('iframe'); iframe.src = placeholder.dataset.url; iframe.loading ='eager';// 立即加载 placeholder.replaceWith(iframe);this.iframes.set(tabId, iframe);this.loadedTabs.add(tabId);// 可以设置最大缓存数量,超了的销毁this.cleanupOldTabs();}cleanupOldTabs(){const maxCache =3;if(this.loadedTabs.size > maxCache){const oldest =this.loadedTabs.values().next().value;// 销毁逻辑...}}}资源预加载也有技巧。如果确定某个iframe很快要用,可以提前建立连接,但不真加载内容:
<!-- 预解析DNS --><linkrel="dns-prefetch"href="https://third-party.com"><!-- 预建立TCP连接 --><linkrel="preconnect"href="https://third-party.com"><!-- 预加载关键资源(慎用,会占用带宽) --><linkrel="prefetch"href="https://third-party.com/widget.html">避免重复初始化。如果iframe只是暂时隐藏(display:none),不要销毁它,下次显示时直接恢复,省得重新加载。但如果确定很久不用,还是destroy掉释放内存。
实际开发中那些又爱又恨的场景
嵌第三方地图
地图组件是iframe的经典用例。但问题来了:怎么知道地图加载完了?怎么在地图上标点?怎么响应地图点击?
// 封装个地图组件,把脏活累活藏起来classMapWidget{constructor(container, apiKey){this.container = container;this.callbacks =newMap();this.ready =false;this.iframe = document.createElement('iframe');this.iframe.src =`https://maps.example.com/embed?key=${apiKey}&callback=init`;this.iframe.sandbox ='allow-scripts allow-same-origin';// 监听地图ready消息this._setupListener(); container.appendChild(this.iframe);}_setupListener(){ window.addEventListener('message',(e)=>{if(e.origin !=='https://maps.example.com')return;switch(e.data.type){case'MAP_READY':this.ready =true;this._flushQueue();// 处理队列中的调用break;case'MARKER_CLICKED':const cb =this.callbacks.get(`marker-${e.data.markerId}`);if(cb)cb(e.data.latLng);break;}});}// 添加标记点,如果地图没准备好先存队列addMarker(lat, lng, options){constcommand=()=>{this.iframe.contentWindow.postMessage({type:'ADD_MARKER', lat, lng, options },'*');};if(this.ready)command();elsethis.commandQueue.push(command);}// 销毁时清理destroy(){this.iframe.remove();// 清理监听器...}}嵌支付页面
支付场景对安全要求极高,而且通常需要知道支付结果。这里的关键是支付完成后怎么通知父页面,以及怎么防止中间人攻击。
<!-- 支付iframe,沙箱要开得恰到好处 --><iframeid="paymentFrame"src="https://secure-payment.com/checkout?orderId=123"sandbox="allow-scripts allow-forms allow-top-navigation-by-user-activation"allow="payment"></iframe><script>// 监听支付结果 window.addEventListener('message',(e)=>{if(e.origin !=='https://secure-payment.com')return;if(e.data.type ==='PAYMENT_SUCCESS'){// 验证签名,防止伪造const isValid =verifySignature(e.data.payload, e.data.signature);if(isValid){showSuccessPage(e.data.orderId);}else{reportSecurityIssue('签名验证失败');}}});// 安全起见,支付完成后应该跳转回你的页面,而不是依赖postMessage// 所以还要处理iframe的load事件,检查URL是否变成了回调地址 document.getElementById('paymentFrame').addEventListener('load',(e)=>{try{const url =newURL(e.target.src);if(url.pathname ==='/payment/callback'){const params =newURLSearchParams(url.search);handlePaymentCallback(params);}}catch(err){// 跨域时读src可能报错,要做好容错}});</script>嵌旧系统
这可能是iframe最无奈的用法——新系统做好了,但老系统迁移成本太高,只能先嵌着。这时候最大的问题是样式不统一和通信困难。
// 封装个适配器,让老系统看起来像是新系统的一部分classLegacySystemAdapter{constructor(iframe, legacyOrigin){this.iframe = iframe;this.legacyOrigin = legacyOrigin;this.styleInjected =false;// 尝试注入CSS让外观统一this._injectStyles();// 监听老系统的导航请求,拦截在新系统里处理this._setupNavigationInterceptor();}_injectStyles(){// 等iframe加载完,尝试注入统一样式this.iframe.addEventListener('load',()=>{try{// 如果同源,可以直接操作const doc =this.iframe.contentDocument;const style = doc.createElement('style'); style.textContent =` body { font-family: 'Your New Font', sans-serif !important; } .legacy-header { display: none !important; } /* 隐藏老系统的header */ `; doc.head.appendChild(style);}catch(e){// 跨域的话,只能让老系统自己支持主题参数了this._requestThemeChange();}});}_requestThemeChange(){// 通过postMessage让老系统自己换肤this.iframe.contentWindow.postMessage({type:'SET_THEME',theme:'modern',primaryColor:'#1890ff'},this.legacyOrigin);}// 拦截老系统的页面跳转,改成单页应用的路由切换_setupNavigationInterceptor(){ window.addEventListener('message',(e)=>{if(e.origin !==this.legacyOrigin)return;if(e.data.type ==='NAVIGATE'){ e.preventDefault();// 阻止默认跳转// 在新系统的路由里处理 window.history.pushState(null,'',`/legacy/${e.data.path}`);// 告诉iframe更新内容,而不是整页刷新this.iframe.contentWindow.postMessage({type:'UPDATE_ROUTE',path: e.data.path },this.legacyOrigin);}});}}一刷新就白屏?高度不对?点不动?常见翻车现场急救包
白屏问题
iframe白屏原因太多了,逐个排查:
// 调试神器:监听所有错误 iframe.addEventListener('error',(e)=>{ console.error('iframe加载失败:', e);});// 检查CSP限制,有些网站禁止被嵌套// 看控制台有没有"Refused to display 'xxx' in a frame because..."的报错// 检查X-Frame-Options头,DENY或SAMEORIGIN都会阻止嵌入// 这个得服务端配合,前端无解// 超时处理,长时间加载不出就提示const loadTimeout =setTimeout(()=>{showError('加载超时,请检查网络或刷新重试');},10000); iframe.addEventListener('load',()=>{clearTimeout(loadTimeout);// 但load事件触发不代表真的加载成功了,还要检查内容try{if(iframe.contentDocument.body.innerHTML ===''){showError('页面内容为空');}}catch(e){// 跨域时读不了,只能看天吃饭}});高度自适应
iframe高度不会自动跟着内容变,这是老大难问题。几种解决方案:
// 方案1:如果同源,直接读scrollHeightfunctionadjustHeight(iframe){try{const height = iframe.contentDocument.body.scrollHeight; iframe.style.height = height +'px';}catch(e){// 跨域报错,用方案2}}// 方案2:子页面主动上报高度// 子页面里:setInterval(()=>{const height = document.body.scrollHeight; window.parent.postMessage({type:'RESIZE', height},'*');},200);// 节流一下,别发太频繁// 父页面里: window.addEventListener('message',(e)=>{if(e.data.type ==='RESIZE'){ iframe.style.height = e.data.height +'px';}});// 方案3:用ResizeObserver(现代浏览器)// 子页面:const observer =newResizeObserver((entries)=>{const height = entries[0].contentRect.height; window.parent.postMessage({type:'RESIZE', height}, parentOrigin);}); observer.observe(document.body);点击穿透或点不动
有时候iframe上面的蒙层遮不住它,或者点击没反应。这是因为iframe是独立的窗口,z-index在它身上表现怪异。
/* 方案1:用pointer-events控制 */.overlay{position: fixed;top: 0;left: 0;right: 0;bottom: 0;background:rgba(0,0,0,0.5);z-index: 9999;}/* 当显示蒙层时,让iframe不接受鼠标事件 */.has-overlay iframe{pointer-events: none;}/* 方案2:如果必须和iframe交互,用wrapper包一层 */.iframe-wrapper{position: relative;}.iframe-wrapper::before{content:'';position: absolute;top: 0;left: 0;right: 0;bottom: 0;z-index: 1;/* 在iframe之上 */display: none;}.iframe-wrapper.disabled::before{display: block;/* 显示遮罩 */}移动端适配灾难
移动端iframe问题特别多:缩放不对、滚动冲突、键盘弹起时布局错乱…
<!-- 确保viewport设置正确 --><iframesrc="https://example.com"style="width: 100%;border: none;"scrolling="no"><!-- 移动端通常需要禁用iframe内部滚动,由父容器控制 --></iframe><script>// 处理iOS的橡皮筋效果导致的滚动问题let startY =0; iframe.addEventListener('touchstart',(e)=>{ startY = e.touches[0].pageY;}); iframe.addEventListener('touchmove',(e)=>{const scrollTop = iframe.contentWindow.pageYOffset;const scrollHeight = iframe.contentWindow.document.body.scrollHeight;const clientHeight = iframe.contentWindow.innerHeight;const y = e.touches[0].pageY;// 在顶部还往上拉,或者在底部还往下拉,就阻止默认行为if((scrollTop <=0&& y > startY)||(scrollTop + clientHeight >= scrollHeight && y < startY)){ e.preventDefault();}});</script>老司机私藏技巧:动态注入、父子通信封装、防XSS小妙招
动态注入内容的高级玩法
有时候你需要往iframe里动态塞代码,但srcdoc不够灵活。可以用Blob URL:
functioncreateIframeWithContent(html, css, js){const fullHtml =` <!DOCTYPE html> <html> <head> <style>${css}</style> </head> <body> ${html} <script> try { ${js} } catch(e) { window.parent.postMessage({type: 'ERROR', message: e.message}, '*'); } <\/script> </body> </html> `;// 创建Blob URL,比data URL长度限制小,而且性能更好const blob =newBlob([fullHtml],{type:'text/html'});const url =URL.createObjectURL(blob);const iframe = document.createElement('iframe'); iframe.src = url;// 清理Blob URL(iframe加载完后就可以清了) iframe.addEventListener('load',()=>{URL.revokeObjectURL(url);});return iframe;}// 应用场景:实时预览用户输入的代码const previewIframe =createIframeWithContent('<div></div>','body { margin: 0; padding: 20px; }','document.getElementById("app").innerHTML = "Hello World";');父子通信封装成Promise风格
原始的postMessage是回调式的,封装成Promise更好用:
classIframeBridge{constructor(iframe, targetOrigin){this.iframe = iframe;this.targetOrigin = targetOrigin;this.pending =newMap();this.id =0; window.addEventListener('message',this._handleMessage.bind(this));}// 调用子页面的方法,返回Promiseinvoke(method, params, timeout =5000){returnnewPromise((resolve, reject)=>{const id =++this.id;const timer =setTimeout(()=>{this.pending.delete(id);reject(newError('调用超时'));}, timeout);this.pending.set(id,{ resolve, reject, timer });this.iframe.contentWindow.postMessage({type:'INVOKE', id, method, params },this.targetOrigin);});}_handleMessage(event){if(event.origin !==this.targetOrigin)return;const data = event.data;if(data.type ==='RESULT'&&this.pending.has(data.id)){const{ resolve, reject, timer }=this.pending.get(data.id);clearTimeout(timer);this.pending.delete(data.id);if(data.error)reject(newError(data.error));elseresolve(data.result);}}// 销毁时清理destroy(){this.pending.forEach(({ timer })=>clearTimeout(timer));this.pending.clear();}}// 使用const bridge =newIframeBridge(iframe,'https://child.com');try{const result =await bridge.invoke('getUserInfo',{userId:123}); console.log(result);}catch(e){ console.error('调用失败:', e);}防XSS的终极方案
如果iframe里的内容完全不可信(比如用户提交的HTML),除了sandbox,还可以:
// 用CSP进一步限制const csp =` default-src 'none'; script-src 'none'; /* 完全禁止脚本 */ style-src 'unsafe-inline'; /* 允许内联样式,但最好也限制 */ img-src data: https:; `;const html =` <meta http-equiv="Content-Security-Policy" content="${csp.replace(/\n/g,' ')}"> <div>${userContent}</div> `;// 或者用srcdoc配合sandbox="allow-same-origin",然后自己控制所有交互// 这样即使内容有恶意脚本,也执行不了你以为这就完了?其实iframe还能这么玩
微前端沙箱的野路子
有些极端场景,你需要在一个页面里跑多个版本的React或Vue,常规方案会冲突。用iframe做沙箱,每个子应用跑在自己的环境里,再通过postMessage通信,虽然通信 overhead 大点,但隔离性无敌。
// 搞个"代理"iframe,里面只跑JS逻辑,不显示const sandboxIframe = document.createElement('iframe'); sandboxIframe.style.display ='none'; sandboxIframe.srcdoc =` <script> // 在这里加载React 16,而父页面用React 18 importScripts('https://cdn.react16.com/bundle.js'); window.addEventListener('message', (e) => { // 执行渲染逻辑,把结果通过postMessage传回去 const result = React16.renderToString(e.data.component); e.source.postMessage({result}, e.origin); }); <\/script> `; document.body.appendChild(sandboxIframe);跨域存储方案
localStorage不能跨域,但你可以用iframe做个"存储服务":
// 在storage.example.com建个页面,专门管存取// 其他域通过iframe嵌它,用postMessage读写// 存储服务页面代码: window.addEventListener('message',(e)=>{const allowedOrigins =['https://app1.com','https://app2.com'];if(!allowedOrigins.includes(e.origin))return;if(e.data.type ==='SET'){ localStorage.setItem(e.data.key, e.data.value); e.source.postMessage({success:true}, e.origin);}elseif(e.data.type ==='GET'){const value = localStorage.getItem(e.data.key); e.source.postMessage({value}, e.origin);}});打印控制
iframe可以用来做打印预览,只打印特定区域:
functionprintSection(htmlContent){const iframe = document.createElement('iframe'); iframe.style.position ='absolute'; iframe.style.left ='-9999px'; iframe.srcdoc =` <html> <head> <link rel="stylesheet" href="/print-styles.css"> <style>@media print { body { margin: 0; } }</style> </head> <body>${htmlContent}</body> </html> `; document.body.appendChild(iframe); iframe.addEventListener('load',()=>{ iframe.contentWindow.print();// 调用打印对话框// 打印完后清理setTimeout(()=> iframe.remove(),1000);});}安全地执行第三方脚本
有些业务需要嵌入第三方统计代码或广告脚本,但又怕它们乱搞。可以扔iframe里,严格控制权限:
<iframesandbox="allow-scripts allow-same-origin"srcdoc=" <script> // 第三方脚本在这里跑,访问不了父页面DOM // 但可以通过postMessage上报数据 window.parent.postMessage({type: 'TRACK', event: 'pageview'}, '*'); <\/script> "></iframe>写到这差不多也该收笔了。iframe这东西,你说它老旧吧,确实有点;但要说它能被完全替代,那也不现实。关键是理解它的原理、掌握它的脾气,该用的时候大胆用,该避的坑提前绕。
最后送各位一句话:技术没有银弹,iframe也不是洪水猛兽。把它当成工具箱里的一把钝刀,虽然不够锋利,但有时候偏偏只有它能砸开那个核桃。好好打磨,照样能切菜。
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!
