使用@page 边距 at 规则 + modern-screenshot 定制打印输出内容

定制打印输出

📦核心

sh 复制代码
npm i modern-screenshot

🖥️ 代码

ts 复制代码
const printReport = async () => {
  // 浏览器版本检查
  const chromeVersion = navigator.userAgent.match(/Chrome\/(\d+)/)?.[1];
  if (!(chromeVersion && parseInt(chromeVersion) >= 131)) {
    await showVersionWarning();
  }

  // 创建打印iframe
  const printWindow = document.createElement('iframe');
  printWindow.style.display = 'none';
  
  printWindow.onload = async function () {
    // 处理logo
    const iconDataUrl = await domToDataUrl(logo.value!, { quality: 1 });
    
    // 克隆报表内容
    const reportClone = report.value.cloneNode(true) as HTMLDivElement;
    reportClone.querySelector('footer')!.remove();
    reportClone.querySelector('header')!.remove();
    
    // 克隆节点替换原节点
    document.querySelector('.report-container')!.replaceChild(reportClone, report.value);
    const foreignObjectSvgElement = await domToForeignObjectSvg(reportClone, {
      quality: 1,
      backgroundColor: '#fff'
    });
    // 重新替换回来
    document.querySelector('.report-container')!.replaceChild(report.value, reportClone);

    // 注入打印样式
    const linkElement = printWindow.contentWindow!.document.createElement('link');
    const styleElement = printWindow.contentWindow!.document.createElement('style');
    linkElement.type = 'text/css';
    linkElement.rel = 'stylesheet';
    linkElement.href = printCSS;
    
    // 注入动态样式
    styleElement.innerHTML = `
      :root {
        --file-no: "${documentNo}";
      }
      @page {
        @top-center {
          content: url("${iconDataUrl}") / "${companyName}";
        }
      }
    `;

    // 添加样式和内容
    printWindow.contentWindow!.document.head.appendChild(linkElement);
    printWindow.contentWindow!.document.head.appendChild(styleElement);
    printWindow.contentWindow!.document.body.appendChild(
      foreignObjectSvgElement.querySelector('.app-container')!
    );

    // 自动调整内容高度
    const content = printWindow.contentWindow!.document.querySelector('.test-data') as HTMLElement;
    content.style.height = 'auto';
    content.style.blockSize = 'auto';

    // 清理资源
    printWindow.contentWindow!.addEventListener('afterprint', () => {
      document.body.removeChild(printWindow);
      myChart?.off('finished');
    });

    // 等待样式加载完成后打印
    linkElement.addEventListener('load', () => {
      printWindow.contentWindow!.print();
    });
  };

  // 初始化iframe
  printWindow.src = 'about:blank';
  document.body.appendChild(printWindow);
};

printCSS.css

css 复制代码
@page {
  /* size: A4; */
  /* margin: 0; */
  margin-left: 0pt;
  margin-right: 0pt;
  margin-bottom: 3lh;
  margin-top: 3lh;

  @top-left {
    content: "\A \A Fender Test Report";
    font-family: 'Times New Roman';
    font-size: 12pt;
    font-weight: bold;
    white-space: pre;
    padding-left: 20px;
  }

  @top-right {
    content: "\A \A" var(--file-no);
    font-family: 'Times New Roman';
    font-size: 0.75em;
    font-weight: normal;
    white-space: pre;
    padding-right: 20px;
  }

  @bottom-center {
    content: "Footer Content";
    font-family: 'Times New Roman';
    font-size: 10pt;
    white-space: pre;
  }
}

✨优点

  1. 自定义页眉与页脚且不只限于文本
  2. 直接复用页面样式,可以使用框架组件快速构建页面

🔦注意

SVG擅长的是​​绘制几何对象​ ​,而非​​承载文档流​ ​,SVG 采用基于坐标的绝对定位(通过 xy 属性定义位置),无法像 HTML/CSS 那样实现自适应流式布局。元素无法根据容器大小自动调整位置或换行。

ts 复制代码
    // 克隆节点替换原节点
    document.querySelector('.report-container')!.replaceChild(reportClone, report.value);
    const foreignObjectSvgElement = await domToForeignObjectSvg(reportClone, {
      quality: 1,
      backgroundColor: '#fff'
    });
    // 重新替换回来
    document.querySelector('.report-container')!.replaceChild(report.value, reportClone);

此处就是通过HTML来处理文档流变化后的效果并通过svg固定下来

💡兼容性(至2025.03.30)

相关推荐
kyriewen1 天前
我手写了一个 EventEmitter,面试官追问了 6 个问题——第 4 个我没答上来
前端·javascript·面试
IT_陈寒1 天前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
小林攻城狮1 天前
使用 Transport 节流解决 Vercel AI SDK 流式渲染卡死问题
前端·react.js
前端缘梦1 天前
告别 TS 运行时类型漏洞!Zod 完整入门实战教程(前端 / 全栈必备)
前端·react.js·全栈
the_answer1 天前
Webpack vs Vite 深度对比分析
前端·webpack
转转技术团队1 天前
验证码识别实战:前端不写页面,改训模型了?
前端
MomentYY1 天前
Temperature:AI 的“脑洞旋钮”
前端·llm·ai编程
远航_1 天前
OpenSpec 完整详细介绍
前端·后端
召钱熏1 天前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
SkyWalking中文站1 天前
认识 Horizon UI · 1/17:SkyWalking 新一代可观测性控制台
运维·前端·监控