JavaScript 打印页面指定元素,且保留样式方法。

124 阅读2分钟

这两天有个需求:要使用 js 打印页面指定元素,且保留样式。

  • 以前的打印基本都是使用 iframe 嵌入预先写好的打印模板页面,再执行打印。
  • 但是这次只是打印页面的表格,且每次都是动态渲染的,再写打印模板感觉就是重复的无用功。
  • 所以决定封装一个方法直接在页面上操作打印表格元素

网上搜索的方案

方案1

let h = window.open('打印窗口', '_blank');
h.document.write(document.getElementById('print-cold-table')?.innerHTML);
h.document.close();
h.print();
h.close();

看了下网上的大部分方案都是新开一个窗口,写入元素内容,执行打印。

  • 这个方案是没有保留样式的,且新开窗口感觉不友好。

方案2

点击打印时,将页面其他元素隐藏,只显示需打印的元素。

  • 感觉对于用户来说还是有点奇怪,突然页面空了一块。

我的整合方案

使用动态创建 iframe 将元素内容与需要的样式复制到 iframe 中,再执行打印。

  • 优点:封装方法,操作简单,方便复用。
  • 缺点:每次打印性能上可能不够好

效果预览

动画.gif

代码

/**
 * 在打印预览中打印指定元素,并设置样式。
 * @example
 * printElement('#print-table', {
 *   bodyStyle: {
 *     padding: '10px',
 *     backgroundColor: 'red',
 *   },
 * });
 * @param selector - 要打印的元素的 CSS 选择器。
 * @param styles - iframe 的 style 配置对象。
 *   @property {any} style - iframe 的基本样式。
 *   @property {any} bodyStyle - iframe 的 body 样式。
 *   @property {any} htmlStyle - iframe 的 html 样式。
 * @returns
 */
export function printDom(selector: string, styles?: { iframeStyle?: any; bodyStyle?: any; htmlStyle?: any }) {
  // 获取需要打印的元素
  const element = document.querySelector(selector);
  if (!element) {
    console.error(`Element with query selector ${selector} not found.`);
    return;
  }

  // 创建打印的 iframe
  const iframe: any = document.createElement('iframe');
  // 设置 iframe 的样式
  Object.assign(iframe.style, {
    display: 'none',
    width: '100%',
    height: 'auto',
    ...(styles?.iframeStyle || {}),
  });
  document.body.appendChild(iframe);

  // 获取 iframe 的内置对象
  const iframeDoc: any = iframe.contentDocument;
  const iframeHtml = iframeDoc.documentElement;
  const iframeHead = iframeDoc.head;
  const iframeBody = iframeDoc.body;

  // 获取元素需要的样式
  const elementStyles = getComputedStyle(element);
  // 将元素需要的样式添加到 iframe
  for (let i = 0; i < elementStyles.length; i++) {
    const styleName = elementStyles[i];
    iframeBody.style[styleName] = elementStyles.getPropertyValue(styleName);
  }

  try {
    // 获取元素所在页面上的样式
    const styleSheets = Array.from(document.styleSheets).map((styleSheet) =>
      Array.from(styleSheet.cssRules)
        .map((cssRule) => cssRule.cssText)
        .join('\n'),
    );
    // 将元素所在页面的样式添加到 iframe
    const styleElement = document.createElement('style');
    styleElement.innerHTML = styleSheets.join('\n');
    iframeHead.appendChild(styleElement);
  } catch (e) {
    console.error(e);
  }

  // 获取元素的内容
  const elementContent = element.outerHTML;
  // 将元素的内容添加到 iframe
  iframeBody.innerHTML = elementContent;

  // 设置 iframe.body 和 iframe.html 的样式与添加自定义的一些样式
  Object.assign(iframeBody.style, {
    width: '100%',
    height: 'auto',
    ...(styles?.bodyStyle || {}),
  });
  Object.assign(iframeHtml.style, {
    width: '100%',
    height: 'auto',
    ...(styles?.htmlStyle || {}),
  });

  // 执行打印
  iframe.contentWindow.print();

  // 打印完成后移除 iframe
  setTimeout(() => {
    document.body.removeChild(iframe);
  }, 1000);
  return iframe;
}

也可直接安装 js-xxx 工具库直接使用

// npm i js-xxx
import { printDom } from 'js-xxx';

printDom('#print-table', {
  bodyStyle: {
    padding: '10px',
    backgroundColor: 'red',
  },
});