如何使用ReactJS Typescript导出iframe内容为PDF
解决iframe加载SFMC邮件模板的PDF导出问题
你遇到的核心问题是iframe内的动态内容(尤其是SFMC邮件的图片、样式)在PDF导出时无法正确渲染,这是因为常用的PDF工具默认难以处理iframe的独立渲染上下文,以及资源加载时机、跨域限制等问题。以下是针对性的解决方案:
方案一:优化react-to-print配置,适配iframe内容
通过监听iframe加载状态,手动获取内部文档内容,确保渲染完成后再执行打印,同时开启背景渲染选项。
import React, { useRef } from 'react'; import { useReactToPrint } from 'react-to-print'; const EmailTemplateViewer = () => { const iframeRefs = useRef<HTMLIFrameElement[]>([]); const handlePrint = (index) => { const printFn = useReactToPrint({ content: () => { const iframe = iframeRefs.current[index]; if (!iframe) return null; const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; if (!iframeDoc) return null; // 克隆iframe内容并添加PDF适配样式 const printContent = iframeDoc.body.cloneNode(true); const style = document.createElement('style'); style.textContent = ` body { margin: 0; padding: 20px; } @page { size: A4; margin: 1cm; } `; printContent.appendChild(style); // 创建临时容器挂载打印内容 const tempContainer = document.createElement('div'); tempContainer.appendChild(printContent); document.body.appendChild(tempContainer); return tempContainer; }, onAfterPrint: () => { // 打印完成后清理临时元素 const tempContainer = document.querySelector('div:last-child'); if (tempContainer) document.body.removeChild(tempContainer); }, printOptions: { background: true, // 强制打印背景图和颜色 }, }); printFn(); }; return ( <div className="w-[100%] flex justify-between items overflow-x-auto mb-2 flex-col"> <div>{renderDataTable()}</div> <div className="flex flex-row overflow-x-auto w-[95%]"> {emailData.map((email: any, index: any) => ( <div className="mr-2 bg-[#ededed] rounded-sm p-2 flex flex-col justify-center items-center" key={index} > <h1 className="mb-2 text-[18px] p-2">{email.name}</h1> <div> <iframe ref={(el) => (iframeRefs.current[index] = el)} srcDoc={emailHtmlCodes[index] ? emailHtmlCodes[index] : email.htmlCode} title={`my-iframe-${index}`} width={650} height={600} onLoad={() => console.log(`模板${index}加载完成`)} ></iframe> </div> <div className="flex justify-center items-center mt-2"> <button className="text-white rounded cursor-pointer bg-blue-500 px-4 py-2" onClick={() => handlePrint(index)} > Print PDF </button> </div> </div> ))} </div> </div> ); }; export default EmailTemplateViewer;
关键要点:
- 用
iframeRefs保存每个iframe的引用,方便获取内部文档 - 监听
onLoad事件确保模板资源(图片、样式)加载完成 - 开启
background: true解决背景图/颜色无法打印的问题 - 打印后清理临时容器,避免污染DOM
方案二:html2canvas + jsPDF组合渲染
手动控制iframe内容的渲染时机,先将iframe内容转为Canvas,再生成PDF,能更好处理跨域资源和动态内容。
import React, { useRef } from 'react'; import html2canvas from 'html2canvas'; import jsPDF from 'jspdf'; const EmailTemplateViewer = () => { const iframeRefs = useRef<HTMLIFrameElement[]>([]); const handlePrintPDF = async (index) => { const iframe = iframeRefs.current[index]; if (!iframe) return; // 等待iframe完全加载 await new Promise(resolve => { if (iframe.contentDocument?.readyState === 'complete') resolve(true); else iframe.addEventListener('load', resolve, { once: true }); }); const iframeBody = iframe.contentDocument?.body; if (!iframeBody) return; // 渲染iframe内容为Canvas,配置跨域支持 const canvas = await html2canvas(iframeBody, { useCORS: true, // 允许加载跨域图片 allowTaint: true, scale: 2, // 提高分辨率,避免PDF模糊 }); // 生成适配A4尺寸的PDF const pdf = new jsPDF('p', 'mm', 'a4'); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, 0, pdfWidth, pdfHeight); pdf.save(`${emailData[index].name}.pdf`); }; return ( <div className="w-[100%] flex justify-between items overflow-x-auto mb-2 flex-col"> <div>{renderDataTable()}</div> <div className="flex flex-row overflow-x-auto w-[95%]"> {emailData.map((email: any, index: any) => ( <div className="mr-2 bg-[#ededed] rounded-sm p-2 flex flex-col justify-center items-center" key={index} > <h1 className="mb-2 text-[18px] p-2">{email.name}</h1> <div> <iframe ref={(el) => (iframeRefs.current[index] = el)} srcDoc={emailHtmlCodes[index] ? emailHtmlCodes[index] : email.htmlCode} title={`my-iframe-${index}`} width={650} height={600} ></iframe> </div> <div className="flex justify-center items-center mt-2"> <button className="text-white rounded cursor-pointer bg-blue-500 px-4 py-2" onClick={() => handlePrintPDF(index)} > Print PDF </button> </div> </div> ))} </div> </div> ); }; export default EmailTemplateViewer;
关键要点:
- 用Promise确保iframe内容完全加载后再渲染
- 配置
useCORS: true解决跨域图片无法渲染的问题(需SFMC图片允许跨域访问) - 设置
scale:2提升Canvas分辨率,避免PDF导出后模糊 - 自动计算PDF尺寸,适配A4页面
方案三:预处理SFMC邮件模板资源
如果上述方案仍有问题,大概率是SFMC模板的资源(图片、样式)存在访问限制,可提前将资源转为内联形式:
// 预处理函数:将HTML中的图片转为base64 const convertImagesToBase64 = async (html) => { const doc = new DOMParser().parseFromString(html, 'text/html'); const imgs = doc.querySelectorAll('img'); for (const img of imgs) { const src = img.src; if (!src.startsWith('data:')) { try { const response = await fetch(src); const blob = await response.blob(); const reader = new FileReader(); await new Promise(resolve => { reader.onload = resolve; reader.readAsDataURL(blob); }); img.src = reader.result as string; } catch (e) { console.error('图片转base64失败:', e); } } } return doc.documentElement.outerHTML; }; // 在组件中调用预处理 useEffect(() => { const processTemplates = async () => { const processedData = await Promise.all( emailData.map(async (email) => ({ ...email, htmlCode: await convertImagesToBase64(email.htmlCode) })) ); setEmailData(processedData); }; processTemplates(); }, []);
关键要点:
- 将图片转为base64内联到HTML中,避免依赖外部资源请求
- 确保SFMC模板的样式为内联样式,避免外部样式表加载失败
- 处理动态脚本,确保模板中的个性化内容在iframe中正常渲染
通用注意事项
- 必须等待iframe的
readyState变为complete后再触发PDF导出 - 跨域资源需配置CORS,或转为base64内联
- 内联样式比外部样式表更适合PDF渲染
- 提高渲染分辨率可避免PDF模糊
内容的提问来源于stack exchange,提问作者Student
相关产品推荐
相关产品推荐

