React.js中iframe内嵌HTML脚本失效及跳转回调监听实现
React中iframe加载HTML字符串并执行脚本、监听跳转的实现方案
问题场景
对接支付系统时,调用API返回的htmlContent内容需要渲染到iframe中,实际运行时iframe内部脚本无法执行,无法触发自动表单提交、跳转逻辑,也无法捕获支付成功的回调地址。
原有实现代码如下:
自定义IFrame组件
const IFrame = ({ children, ...props }: any) => { const [contentRef, setContentRef] = useState<any>(null); const mountNode = contentRef?.contentWindow?.document?.body; return ( // eslint-disable-next-line jsx-a11y/iframe-has-title <iframe {...props} ref={setContentRef} style={{ border: 0 }} onLoad={(e: any) => { console.log('page loaded', e.target); }} > {mountNode && createPortal(children, mountNode)} </iframe> ); };
页面调用逻辑
const html = "<html>Hello</html><script>alert('world');</script>"; return ( <IFrame> <div dangerouslySetInnerHTML={{ __html: html }} /> </IFrame> );
运行上述代码时,iframe内没有触发预期的alert弹窗,无法满足支付页自动跳转、监听回调地址的需求。
问题根因
脚本无法执行的核心原因有两点:
- 浏览器内置XSS防护规则限制:通过
innerHTML(即React的dangerouslySetInnerHTML)插入的<script>标签默认不会被解析执行,该规则对iframe内外的DOM插入都生效 - 实现逻辑缺陷:通过
createPortal将内容挂载到iframe已有文档的body节点,没有完整写入HTML文档上下文,支付系统返回的自动提交表单、跳转类脚本依赖完整文档环境,无法正常触发
可行实现方案
优先使用iframe原生srcdoc属性加载完整HTML字符串,该方式会让浏览器正常解析并执行文档内的所有脚本,兼容性覆盖所有现代浏览器。同时通过iframe的load事件监听页面跳转,捕获回调地址。
可直接复用的支付IFrame组件
import { useRef, useState, useEffect } from 'react'; const PaymentIFrame = ({ htmlContent, onUrlChange, ...props }) => { const iframeRef = useRef(null); const [currentUrl, setCurrentUrl] = useState(''); // 兼容不支持srcdoc的老旧浏览器,可按需启用 // useEffect(() => { // const iframe = iframeRef.current; // if (!iframe || !htmlContent) return; // const doc = iframe.contentDocument || iframe.contentWindow.document; // doc.open(); // doc.write(htmlContent); // doc.close(); // }, [htmlContent]); const handleIframeLoad = () => { try { // 同域场景下可直接读取iframe当前地址 const newUrl = iframeRef.current.contentWindow.location.href; if (newUrl !== currentUrl) { setCurrentUrl(newUrl); onUrlChange?.(newUrl); // 匹配到支付成功回调路径时执行业务逻辑 if (newUrl.includes('/pay/callback/success')) { console.log('捕获到支付成功跳转', newUrl); } } } catch (err) { // 跨域场景下无法直接读取location,可通过postMessage接收支付方推送的结果 } }; return ( <iframe {...props} ref={iframeRef} srcDoc={htmlContent} onLoad={handleIframeLoad} style={{ border: 0, width: '100%', height: '100%' }} title="支付页面" /> ); };
组件调用方式
// 直接传入支付接口返回的完整HTML字符串即可,无需额外处理 const payHtml = `<html> <body> 支付跳转中... <script> alert('world'); // 此处为支付系统自带的自动提交表单、跳转逻辑,会正常执行 </script> </body> </html>`; return ( <PaymentIFrame htmlContent={payHtml} onUrlChange={(url) => console.log('检测到iframe地址变化:', url)} /> );
注意事项
- 不要使用
dangerouslySetInnerHTML向iframe内插入脚本内容,浏览器默认不会执行这类脚本,必须通过srcdoc或者document.write的方式写入完整HTML文档 - 跨域场景下无法直接读取iframe的
location.href,两种兜底方案:- 配置支付系统的跳转回调地址为自身站点的同域地址,跳转回同域后即可正常读取URL判断支付结果
- 若支付渠道支持postMessage通信,可监听window的
message事件接收支付结果通知
- 传入的HTML内容必须完全来自可信的支付接口返回,不要拼接用户可控参数,避免XSS风险
内容的提问来源于stack exchange,提问作者Ahmad Echem
相关产品推荐
相关产品推荐

