You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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中正常渲染

通用注意事项

  1. 必须等待iframe的readyState变为complete后再触发PDF导出
  2. 跨域资源需配置CORS,或转为base64内联
  3. 内联样式比外部样式表更适合PDF渲染
  4. 提高渲染分辨率可避免PDF模糊

内容的提问来源于stack exchange,提问作者Student

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 18:20:56