Web前端实现自定义页面尺寸打印

项目组最近有一个实现自定义页面尺寸打印的需求:

打印纸张不同尺寸.png

浏览了网上大部分的教程,基本总结如下:

方案一:CSS

使用 CSS 的 @page 规则:通过 @page 规则,可以定义打印页面的尺寸和边距。例如,可以使用 @page 规则来定义 A4 纸张的尺寸和边距,或者根据需要定义其他自定义尺寸的页面。

@page {
  size: 210mm 297mm; /* A4 尺寸 */
  /* 或者自定义页面尺寸 */
}

方案二:Javascript

使用 JavaScript 来控制页面的打印参数,包括页面尺寸、边距等。可以通过 JavaScript 改变打印样式表中的参数,实现自定义页面尺寸的打印。

function setPrintStyles() {
  const iframe = document.createElement('iframe');
  //插入需要打印的目标元素
  document.querySelector(‘body‘).appendChild(iframe)
  //插入style
  …………
}

方案三:利用浏览器自带打印

使用打印预览对话框进行手动设置:在打印预览对话框中,用户可以手动选择页面尺寸、边距等打印参数,从而实现自定义页面尺寸的打印。
打印参数.png

方案比较

  • 针对方案一,弊端在于部分低版本浏览器兼容性较差,不能很好的实现自定义打印效果。
  • 针对方案二,通过js来指定打印参数,麻烦在于需要将整个页面进行复制后(包括样式的复制),再进行打印,兼容性较好,但是代码多。
  • 针对方案三,不推荐,不同浏览器、不同操作系统会有不同的打印方案,难以控制保持一致性。

最终解决方案:封装方案二

最终决定封装一个javascript文件来实现自定义页面尺寸打印。核心部分包括:

  1. 创建一个iframe标签,来容纳页面内容。
  2. 复制页面中的原有style样式。包括head中的link stylesheet、style标签中的样式以及外部样式文件。
  3. 打印完成后移除iframe标签。

完整代码实现:

const defaultOptions = {
    el: '',              //打印目标dom节点
    debug: true,        //打开调试模式,会显示iframe,
    importCss: true,     //引入head 中的link stylesheet
    importStyle: true,   //引入style标签中的样式
    loadCss: [],         //需要载入的第三方样式表
    title: '',          //打印标题
    delay: 300,         //延迟打印时间,确保iframe中的静态资源加载完成
    beforePrintHandle: null,  //打开打印窗口前的钩子函数,可以针对打印文档进行自定义调整,接受一个document参数
    afterPrintHandle: null,   //打印完成的钩子函数
}

let iframe = null
let dom = null

const checkOptions = options => {
    if(!options.el) {
        throw new Error('el must be a nodeType')
    }
    return {
        ...defaultOptions,
        ...options
    }

}
const printf = options => {
    const op = checkOptions(options)
    dom = op.el.cloneNode(true)
    const handle = createIframe(op)
    if(op.beforePrintHandle) {
        op.beforePrintHandle(handle.contentDocument);
    }
    if (op.afterPrintHandle) {
        op.afterPrintHandle();
    }
    setTimeout(() => {
        handle.print();
        if (op.debug === false) {
            removeIframe();
        }
    }, op.delay)
}

const createIframe = (op) => {
    const { debug, importCss, importStyle, loadCss, title} = op
    removeIframe();
    iframe = document.createElement('iframe');
    if(debug === false) {
        iframe.style.display = 'none'
    }
    //插入需要打印的目标元素
    document.querySelector('body').appendChild(iframe)
    iframe.contentDocument.title = title;
    const { body, head } = iframe.contentDocument;
    const contentWindow = iframe.contentWindow;
    //插入head中的link stylesheet
    if(importCss) {
        const stylesheets = document.querySelectorAll("link[rel = 'stylesheet']")
        stylesheets.forEach(item => {
            head.appendChild(item.cloneNode(true))
        })
    }
    //插入style
    if (importStyle) {
        const stylesheets = document.querySelectorAll("style")
        stylesheets.forEach(item => {
            body.appendChild(item.cloneNode(true))
        })
    }
    //插入外部样式文件
    if (Array.isArray(loadCss) && loadCss.length > 0) {
        loadCss.forEach(item => {
            head.appendChild(item)
        })
    }
    body.appendChild(dom)
    return contentWindow;
}
const removeIframe = () => {
    if (iframe) {
        document.querySelector('body').removeChild(iframe)
        iframe = null
    }
}

export default printf

在调用的时候这样使用即可:

// 打印事件处理
const printHandle = () => {
  const style = document.createElement('style');
  style.innerHTML = `@media print { @page {size:${宽}mm ${高}mm!important; margin: 0;padding: 0;} }`;
  window.document.head.appendChild(style);
  printf({
    el: printRef.value,      //打印目标dom节点
    debug: false,            //打开调试模式,会显示iframe,
    importCss: true,         //引入head 中的link stylesheet
    importStyle: true,       //引入style标签中的样式
    delay: 300,              //延迟打印时间,确保iframe中的静态资源加载完成
  });
}
打赏
评论区
头像
    头像
    吾柯
      

    之前作过一个法院项目,里面有卷宗打印需求,调试非常恶心人。