项目组最近有一个实现自定义页面尺寸打印的需求:
浏览了网上大部分的教程,基本总结如下:
方案一: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
…………
}
方案三:利用浏览器自带打印
使用打印预览对话框进行手动设置:在打印预览对话框中,用户可以手动选择页面尺寸、边距等打印参数,从而实现自定义页面尺寸的打印。
方案比较
- 针对方案一,弊端在于部分低版本浏览器兼容性较差,不能很好的实现自定义打印效果。
- 针对方案二,通过js来指定打印参数,麻烦在于需要将整个页面进行复制后(包括样式的复制),再进行打印,兼容性较好,但是代码多。
- 针对方案三,不推荐,不同浏览器、不同操作系统会有不同的打印方案,难以控制保持一致性。
最终解决方案:封装方案二
最终决定封装一个javascript文件来实现自定义页面尺寸打印。核心部分包括:
- 创建一个iframe标签,来容纳页面内容。
- 复制页面中的原有style样式。包括head中的link stylesheet、style标签中的样式以及外部样式文件。
- 打印完成后移除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中的静态资源加载完成
});
}
之前作过一个法院项目,里面有卷宗打印需求,调试非常恶心人。