web打印的奇技淫巧

463 阅读7分钟

web打印的奇技淫巧

背景

  • 公司需要做一个报告打印,内容过长会分为多张纸打印,且每页都必须要有固定的页眉页脚

image-20230427205501406

奇技淫巧一:控制台以打印媒体类型展示

  • devtool -> 渲染/render -> 模拟CSS媒体类型中拥有打印模式,它可以显示你在打印下的样式

image-20230427210302780

@media print {
  .header,
  .footer {
    position: fixed;
  }
}

image-20230427210415756

奇技淫巧二:打印背景

  • 默认不打印背景

image-20230428155815726

  • 设置打印背景

image-20230428155911920

实现

      <button onClick={print}>打印</button>
      <StyleWrapper className="wrapper" ref={wrapperRef}>
        <div className="header" ref={headerRef}>
          日记:GPT的一天
        </div>
        <div className="content">
         // ...
        </div>
        <table className="print-table" ref={tableRef}>
          <thead></thead>
          <tbody></tbody>
          <tfoot></tfoot>
        </table>
        <div className="footer" ref={footerRef}>
          作者:GPT3.5
        </div>
      </StyleWrapper>
      <iframe
        ref={iframeRef}
        title="打印iframe容器"
        width="100%"
        height="600px"
      />
    </>
  • 🤔思路:.content用于html展示的数据,table.print-table(display: none)用于打印时显示,将content拷贝一份到table中,再将content隐藏,table显示就可以在打印预览中显示了
  • 使用了position: fixed后头部和底部占位的解决方案:使用table的theadtfoot,他们可以在每一页中都实现占位的作用

🪧 至于原理,希望大佬们知道的回复我,我在网上一直没有找到这块相关的内容...

/**
  * 打印核心逻辑
  */
function print() {
  if (
    !tableRef.current ||
    !wrapperRef.current ||
    !headerRef.current ||
    !footerRef.current ||
    !iframeRef.current
  )
    return;
  const contentEl = wrapperRef.current.querySelector(
    ".content"
  ) as HTMLDivElement;
​
  const theadEl = tableRef.current.tHead as HTMLTableSectionElement;
  const tfootEl = tableRef.current.tFoot as HTMLTableSectionElement;
  const tbodyEl = tableRef.current.tBodies[0];
  tbodyEl.appendChild(contentEl.cloneNode(true)); // clone主体部分copy到table里
  contentEl.style.display = "none";
​
  // 设置thead、tfoot高度,占位
  const headerHeight = getElHeight(headerRef);
  const footerHeight = getElHeight(footerRef);
  theadEl.style.height = `${headerHeight + 10}px`;
  tfootEl.style.height = `${footerHeight + 10}px`;
​
  // 拷贝打印内容到iframe
  const header = document.head.innerHTML;
  console.log(wrapperRef.current);
  const copyNode = wrapperRef.current.cloneNode(true);
  const iframeDoc = iframeRef.current.contentDocument as Document;
  const iframeWin = iframeRef.current.contentWindow as Window;
  iframeDoc.head.innerHTML = header;
  iframeDoc.body.appendChild(copyNode);
  iframeWin.focus();
  iframeWin.print();
  
  // 复原
  contentEl.style.display = "block";
  iframeDoc.head.innerHTML = "";
  iframeDoc.body.innerHTML = "";
}

给孩子一点鼓励

后续追加的细节

  • 2023-06-05 通过限制网络速度发现,浏览器并不会等待内部异步元素加载(如:图片)完毕后,调出打印界面
IMG_3389

问题:可以看到这个logo并没有下载完毕,可能在高带宽场景复现不了,这方面还是要注意一下

解决方案:对于高延迟异步元素打印时,等待"完成事件"钩子(onload...)触发后再进行打印

  • 2023-06-06 如果希望thead、tfooter每页都显示,一定要在thead中定义tr元素,否则不会每页生效
image-20230606183340414

参考地址:W3C官网:table元素

  • ⚠️ 2023-09-16 发现了chrome print对position: fixed脱离文档流的width: 100%的算法更改。

    • 结论:为了不同版本兼容性,应该将table内容强制设置为100%。

    • 当chrome打印table内容要大于A4纸宽度时,会缩放显示所有A4纸的内容

    • Chrome v115及以下,width: 100%表示显示的最大宽度。比如:table宽1500px,则100% === 1500px(高度同理) image-20230916193549285

    • chrome v116-v119左右,width: 100%表示A4纸的最大宽度。假设,A4宽度为900px,table宽1500px,则100% === 900px(高度同理)

    下图1:chrome v119版本下,table宽为1100px,width: 100%实际上只有A4纸大小的问题!

    下图2:chrome v119版本下,table宽为100%,正常显示

image-20230916184649830 image-20230916192806539
<!-- 测试html,可以自行测试 -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Print Bug🐛</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    body {
      width: 100%;
    }

    .print-iframe {
      width: 100%;
      height: 100%;
      border: none;
      border-top: 1px solid black;
      border-bottom: 1px solid black;
    }

    @media print {
      #print {
        width: 100%;
        background-color: antiquewhite;
      }

      .footer,
      .header {
        position: fixed;
        left: 0;
        width: 100%;
        height: 40px;
        line-height: 40px;
        opacity: 0.5;

        text-align: center;
        font-size: 24px;
        font-weight: bolder;
      }

      .header {
        top: 0;
        background-color: brown;
      }

      .footer {
        bottom: 0;
        background-color: paleturquoise;
      }

      table {
        width: 100%;
      }

      .content {
        /*
          注释这行,tbody的内容宽度为写死的1100px,此宽度要远大于A4的宽度的像素值
          只用强制将该宽度设置为100%即可解决
        */
        /* width: 100% !important; */
      }
    }
  </style>
</head>

<body>
  <button onclick="print()">Print</button>
  <div id="print">
    <div class="header">Header</div>

    <div class="content" style="width: 1100px;">
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
      <div>Tenderloin ham boudin tongue sausage venison short ribs sirloin, kielbasa beef ribs. Strip steak shank
        bresaola
        salami spare ribs kielbasa fatback, cow t-bone flank leberkas sirloin. Jowl pork belly ribeye, corned beef
        sirloin
        chicken salami tail. Rump swine ham shank corned beef short loin, speck turkey pancetta shankle frankfurter.
        Pancetta tail fatback, ground round brisket biltong frankfurter turkey. Ham hock chicken strip steak, salami
        short
        ribs beef ribs pork sirloin pastrami pork loin turducken rump brisket andouille.
      </div>
    </div>

    <table style="display: none; background-color: pink; width: 100%;">
      <thead>
        <tr>
          <td style="width: 100%; height: 50px; background-color: skyblue;"></td>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td id="tableContent"></td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <td style="width: 100%; height: 50px; background-color: rosybrown;"></td>
        </tr>
      </tfoot>
    </table>

    <div class="footer">
      Footer
    </div>

  </div>
</body>
<script>
  function print() {
    const existIframe = document.querySelector(".print-iframe");
    if (existIframe) {
      existIframe.contentWindow.print();
      return;
    }

    const content = document.querySelector("#print").cloneNode(true);
    const tableEl = content.querySelector("table");
    const tbodyContentEl = tableEl.querySelector("#tableContent");
    const contentEl = content.querySelector(".content");
    const contentCp = contentEl.cloneNode(true);

    contentEl.style.display = "none";
    tableEl.style.display = "table";
    tbodyContentEl.appendChild(contentCp);


    const iframe = document.createElement("iframe");
    iframe.className = "print-iframe";
    document.body.appendChild(iframe);

    const iframeDoc = iframe.contentDocument;
    const iframeWin = iframe.contentWindow;

    iframeDoc.body.appendChild(content);
    iframeDoc.head.innerHTML = document.head.innerHTML;
    iframeWin.print();
  }

</script>

</html>

🤔️ 目前该问题没有找到明显的参考地址,且起码会发生在Chrome v115+以上。(如果有大佬查到W3C来源或Chrome功能的来源可以分享一下)

有关打印CSS样式

  • 兼容性:v Chrome 1+
page-break-inside // 针对元素内的分页符
// avoid 避免, always 总是, auto 自动
.div { page-break-inside: avoid } // 该div元素不允许被分割成两页显示

page-break-before // 针对元素之前的分页符
page-break-after // 针对元素之后的分页符
  • 兼容性:chrome v50+

  • 以上样式都可以被break-*替换

break-insde
break-before
break-after

MDN参考地址:developer.mozilla.org/en-US/docs/…

  • @page:只能包含page-properties属性和margin at-rules
// 设置所有页面
@page {
  size: a4;
  margin-top: 4pt;
}

@page :first {} // 设置第一页
@page :left {} // 设置所有奇数页
@page :right {} // 设置所有偶数页

// 设置自定义页面
// @page [custom-name] {}
@page page1 {
  size: a4;
  @top-middle {
    content: "Chapter";
  }
}
.main {
  page: page1;
}

参考:MDN page-properties属性

参考:CSS page属性

  • margin at-rules:只能包含page-margin属性
@page {
  @top-left {
    content: "内容"
  }
}

更多margin at-rules参考:MDN margin at-rules属性