前端打印pdf的方法
最近公司有项目需要用到前端打印pdf的功能,我网上找了几种方法,发现要不就是不合适,要不就是有缺陷(万恶的百度算法,导致打开的网页都是爬虫抄的),本人就在原有的基础上进行了优化和修改。
首先,打印pdf的方法总共有2类,1是直接用window.print(),2是用pdf插件。这里因为需求简单,我们就选择不引用pdf插件,直接用window.print()打印。
既然打印,我们肯定是需要局部打印,不能把打印按钮也打印进去,那么print方法的局部打印主要分3种方法(下面都是我网上抄的):
1.通过开始、结束标记(startprint、endprint)来打印
function doPrint() {
bdhtml=window.document.body.innerHTML;
sprnstr="<!--startprint-->"; //开始打印标识字符串有17个字符
eprnstr="<!--endprint-->"; //结束打印标识字符串
prnhtml=bdhtml.substr(bdhtml.indexOf(sprnstr)+17); //从开始打印标识之后的内容
prnhtml=prnhtml.substring(0,prnhtml.indexOf(eprnstr)); //截取开始标识和结束标识之间的内容
window.document.body.innerHTML=prnhtml; //把需要打印的指定内容赋给body.innerHTML
window.print(); //调用浏览器的打印功能打印指定区域
window.document.body.innerHTML=bdhtml;//重新给页面内容赋值;
return false;
}
2.通过id选择器来替换内容打印,方法类似第一种
function doPrint2(){
if(getExplorer() == "IE"){
pagesetup_null();
}
//根据div标签ID拿到div中的局部内容
bdhtml=window.document.body.innerHTML;
var jubuData = document.getElementById("printcontent").innerHTML;
//把获取的 局部div内容赋给body标签, 相当于重置了 body里的内容
window.document.body.innerHTML= jubuData;
//调用打印功能
window.print();
window.document.body.innerHTML=bdhtml;//重新给页面内容赋值;
return false;
}
function pagesetup_null(){
var hkey_root,hkey_path,hkey_key;
hkey_root="HKEY_CURRENT_USER";
hkey_path="\\Software\\Microsoft\\Internet Explorer\\PageSetup\\";
try{
var RegWsh = new ActiveXObject("WScript.Shell");
hkey_key="header";
RegWsh.RegWrite(hkey_root+hkey_path+hkey_key,"");
hkey_key="footer";
RegWsh.RegWrite(hkey_root+hkey_path+hkey_key,"");
}catch(e){}
}
function getExplorer() {
var explorer = window.navigator.userAgent ;
//ie
if (explorer.indexOf("MSIE") >= 0) {
return "IE";
}
//firefox
else if (explorer.indexOf("Firefox") >= 0) {
return "Firefox";
}
//Chrome
else if(explorer.indexOf("Chrome") >= 0){
return "Chrome";
}
//Opera
else if(explorer.indexOf("Opera") >= 0){
return "Opera";
}
//Safari
else if(explorer.indexOf("Safari") >= 0){
return "Safari";
}
}
3.通过动态创建iframe来打印
//判断iframe是否存在,不存在则创建iframe
var iframe=document.getElementById("print-iframe");
if(!iframe){
var el = document.getElementById("printcontent");
iframe = document.createElement('IFRAME');
var doc = null;
iframe.setAttribute("id", "print-iframe");
iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;');
document.body.appendChild(iframe);
doc = iframe.contentWindow.document;
//这里可以自定义样式
doc.write('<style media="print">@page {size: auto;margin: 0mm;}</style>'); //解决出现页眉页脚和路径的问题
doc.write('<div>' + el.innerHTML + '</div>');
doc.close();
iframe.contentWindow.focus();
}
setTimeout(function(){ iframe.contentWindow.print();},50) //解决第一次样式不生效的问题
if (navigator.userAgent.indexOf("MSIE") > 0){
document.body.removeChild(iframe);
}
这3种方法前2种,其实都是通过替换标签来解决的,但是这种方法有个弊端,就是现在页面会发生改变,而第3种不会出现这种情况,那么我们就用第3种。但是第三种方法网上贴出的代码也有问题,主要问题和解决方法如下:
1.css样式带不进去。
外部的css是带不进iframe里的,很多人因此选择直接用内联样式,或者直接iframe里也引用css,但是这样做又遇到一个问题,如果用内联会很丑,而且无法用sass和less,如果直接引用css,直接写html还好,如果用vue这种环境写,在本地环境下会直接放在style里,生产环境下会生成不知名的几个css,这个时候,我们就需要取出style和link:
doc = iframe.contentWindow.document;
let styles = document.getElementsByTagName('style');
for(let i = 0;i < styles.length;i++)
{
doc.write(styles[i].outerHTML)
}
let links = document.getElementsByTagName('link');
for(let i = 0;i < links.length;i++)
{
doc.write(links[i].outerHTML)
}
如果有deep样式,并且style标签用的是scoped,就需要额外获取data-v作为style的选择器,操作如下:
let data = document.getElementById("printContainer").dataset;
let dataV = "";
for(let key in data)
{
if(key.indexOf("v-")!=-1)
{
dataV = 'data-' + key;
}
}
doc.write(`<div ${dataV}>${_html}</div>`)
2.选择打印的dom的本身会丢失
这个就是乱copy别人代码的毛病了,人家用innterHTML,你就照抄,实际上应该使用outerHTML。
doc.write('<div>' + el.innerHTML + '</div>');
doc.write('<div>' + el.outerHTML+ '</div>');
//就这么简单
3.因为父级选择器,style不起作用
用刚才上面1的方法style确实传进去了,但是因为父级选择器的关系,style还是会不生效,办法也很简单,我们手动给iframe内部加同名父级。
let _html = el.outerHTML;
for(let i = 0;i < parentClassNameList.length;i++)
{
_html = '<div class="' + parentClassNameList[i] + '">' + _html + '</div>'
}
doc.write('<div>' + _html + '</div>');
parentClassNameList为父类名列表,一层一层上去。
4.canvas内部的像素不会被copy
这个就需要额外copy,这里我直接贴出copy代码
let _iframeCanvas = iframe.contentWindow.document.getElementById(_canvasId);
let _currentCanvas = document.getElementById(_canvasId);
let _iframeCtx = _iframeCanvas.getContext("2d");
_iframeCtx.drawImage(_currentCanvas,0,0);
好了,最后我们把所有代码放一起封装一下。
print.js:
const print = {
isPrint:false,
//styles:iframe的style于主页面的是隔离的,需要额外传输
//otherStyle:额外的style,主要用于控制默认打印大小(如:otherStyle:'transform:scale(1.5);transform-origin: top left;',)
//parentClassNameList:由于style可能会带父级的选择器,所以这里也需要模拟父级(由内向外,从domID本身开始算)
//dataV:css scoped后需要添加的选择器,一般使用了deep修改css,都需要使用这个参数
start:(props)=>{
if(print.isPrint) return;
print.isPrint = true;
console.log("print")
let domID = props.domID;
let styles = props.styles;
let links = props.links;
let otherStyle = props.otherStyle;
let parentClassNameList = props.parentClassNameList;
let dataV = props.dataV;
let delay = 500;
if(props.delay) delay = props.delay;
if(!dataV) dataV = "";
let beforePrint = props.beforePrint;
let printComplete = props.printComplete;
let el = document.getElementById(domID);
let iframe=document.getElementById("print-iframe");
if(!iframe){
iframe = document.createElement('IFRAME');
iframe.setAttribute("id", "print-iframe");
iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;z-index:1000');
// iframe.setAttribute('style', 'position:absolute;width:1920px;height:1080px;left:0px;top:0px;z-index:1000');
document.body.appendChild(iframe);
}
let doc = iframe.contentWindow.document;
if(styles)
{
for(let i = 0;i < styles.length;i++)
{
// doc.write(`<style> ${styles[i].innerHTML} </style>`)
doc.write(styles[i].outerHTML)
}
}
if(links)
{
for(let i = 0;i < links.length;i++)
{
doc.write(links[i].outerHTML)
}
}
doc.write('<style media="print">@page {size: auto;margin: 0mm;}</style>'); //解决出现页眉页脚和路径的问题
if(parentClassNameList)
{
let _html = el.outerHTML;
for(let i = 0;i < parentClassNameList.length;i++)
{
_html = `<div class="${parentClassNameList[i]}">${_html}</div>`
}
doc.write(`<div style="position: absolute;left: 0;top: ${index*pageHeight}px" ${dataV}>${_html}</div>`)
}
else
{
doc.write(`<div style="position: absolute;left: 0;top: ${index*pageHeight}px" style="" ${dataV}>${el.outerHTML}</div>`)
}
doc.close();
if(otherStyle)
{
let newEl = iframe.contentWindow.document.getElementById(domID);
newEl.setAttribute('style', otherStyle);
}
iframe.contentWindow.focus();
if(beforePrint)
{
beforePrint(iframe)
}
setTimeout(function(){
let _title = document.title;
document.title = "布匹检验报告" + title;
iframe.contentWindow.print();
document.title = _title;
document.body.removeChild(iframe);
if(printComplete)
{
printComplete(iframe)
}
print.isPrint = false;
},delay) //解决第一次样式不生效和图片还没加载出来的问题
},
startPages:(props)=>{
if(print.isPrint) return;
print.isPrint = true;
console.log("print")
let list = props.list;
let styles = props.styles;
let links = props.links;
let title = props.title;
let delay = 500;
if(props.delay) delay = props.delay;
let pageHeight = props.pageHeight;
let beforePrint = props.beforePrint;
let printComplete = props.printComplete;
let iframe=document.getElementById("print-iframe");
if(!iframe){
iframe = document.createElement('IFRAME');
iframe.setAttribute("id", "print-iframe");
iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;z-index:1000');
// iframe.setAttribute('style', 'position:absolute;width:1920px;height:1080px;left:0px;top:0px;z-index:1000');
document.body.appendChild(iframe);
}
let doc = iframe.contentWindow.document;
if(styles)
{
for(let i = 0;i < styles.length;i++)
{
// doc.write(`<style> ${styles[i].innerHTML} </style>`)
doc.write(styles[i].outerHTML)
}
}
if(links)
{
for(let i = 0;i < links.length;i++)
{
doc.write(links[i].outerHTML)
}
}
doc.write('<style media="print">@page {size: auto;margin: 0mm;}</style>'); //解决出现页眉页脚和路径的问题
list.forEach((item,index)=>{
let domID = item.domID;
let otherStyle = item.otherStyle;
let parentClassNameList = item.parentClassNameList;
let dataV = item.dataV;
if(!dataV) dataV = "";
let el = document.getElementById(domID);
if(parentClassNameList)
{
let _html = el.outerHTML;
for(let i = 0;i < parentClassNameList.length;i++)
{
_html = `<div class="${parentClassNameList[i]}">${_html}</div>`
}
doc.write(`<div style="position: absolute;left: 0;top: ${index*pageHeight}px" ${dataV}>${_html}</div>`)
}
else
{
doc.write(`<div style="position: absolute;left: 0;top: ${index*pageHeight}px" style="" ${dataV}>${el.outerHTML}</div>`)
}
if(otherStyle)
{
let newEl = iframe.contentWindow.document.getElementById(domID);
newEl.setAttribute('style', otherStyle);
}
})
doc.close();
iframe.contentWindow.focus();
if(beforePrint)
{
beforePrint(iframe)
}
setTimeout(function(){
let _title = document.title;
document.title = "布匹检验报告" + title;
iframe.contentWindow.print();
document.title = _title;
document.body.removeChild(iframe);
if(printComplete)
{
printComplete(iframe)
}
print.isPrint = false;
},delay)
}
}
export default print
可以看到,我这里还加了多页面打印的版本。
使用:
let styles = document.getElementsByTagName('style');
print.start({
domID:'testPrint',//需要打印的domID
styles:styles,
parentClassNameList:['dashboard','main-content','page'],//需要打印的dom的上层class
beforePrint:(iframe)=>{
//如果有canvas打印需求,打开下面代码
// let _canvasId = "canvas";//canvas的id
// let _iframeCanvas = iframe.contentWindow.document.getElementById(_canvasId);
// let _currentCanvas = document.getElementById(_canvasId);
// let _iframeCtx = _iframeCanvas.getContext("2d");
// _iframeCtx.drawImage(_currentCanvas,0,0);
}
})
let styles = document.getElementsByTagName('style');
let links = document.getElementsByTagName('link');
let data = document.getElementById("printContainer").dataset;
let dataV = "";
for(let key in data)
{
if(key.indexOf("v-")!=-1)
{
dataV = 'data-' + key;
}
}
let print1 = document.getElementById('print1');
print.startPages({
styles:styles,
links:links,
list:[
{
domID:"print1",
//这里的额外样式是用来缩放页面,使页面刚好撑开到A4纸的大小
otherStyle:`height:${print1.offsetHeight*1.82};transform:scale(${1.82});transform-origin: top left;`,
parentClassNameList:['real-page','print-page','summary','his-right','his-container','comp'],
dataV:dataV,
},
{
domID:"print2",
parentClassNameList:['real-page','print-page','details','his-right','his-container','comp'],
dataV:dataV,
}
],
beforePrint:(iframe)=>{
let _canvasId = olaHisMap.value.canvasID;
let _iframeCanvas = iframe.contentWindow.document.getElementById(_canvasId);
let _currentCanvas = document.getElementById(_canvasId);
let _iframeCtx = _iframeCanvas.getContext("2d");
_iframeCtx.drawImage(_currentCanvas,0,0);
}
})
注意:因为缩放功能是用scale实现的,多页面会造成排版遮盖,所以额外样式需要垫高页面。
最后说一下,这个做法vue和react都适用,但是ID需要自己额外赋一下。

发表评论