我們知道瀏覽器有一個(gè)
window.print()
可以調(diào)起瀏覽器自帶的打印頁(yè)面的功能,它會(huì)把當(dāng)前文檔的body生成一個(gè)PDF進(jìn)行打印盟猖,最新在業(yè)務(wù)開(kāi)發(fā)中就使用了它來(lái)實(shí)現(xiàn)了一個(gè)局部頁(yè)面打印的功能,雖然看起來(lái)很簡(jiǎn)單征懈,但還是遇到了一些問(wèn)題枝秤,例如:1. 微前端場(chǎng)景下的樣式丟失 2. 尾部預(yù)留空白頁(yè) 3. table重復(fù)打印tfooter等問(wèn)題零蓉。直接跑demo點(diǎn)這里
1. 說(shuō)說(shuō)在React中實(shí)現(xiàn)一個(gè)局部打印功能的組件
實(shí)現(xiàn)的思路就是使用iframe篮幢,把iframe的body替換成需要打印的DOM大刊,然后調(diào)用iframe里的window.print()
實(shí)現(xiàn)打印功能。根據(jù)思路我們實(shí)現(xiàn)如下組件:
'use strict';
// 生成一個(gè)唯一的guid
function getGuid() {
let s = [];
let hexDigits = "0123456789abcdef";
for (let i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4";
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = "-";
let uuid = s.join("");
return uuid;
}
const style = `<style>
@page: pseudo-class {
size: A4 landscape;
margin: 2 cm;
}
@media print {
section {page-break-before: always;}
h1 {page-break-after: always;}
p {
page-break-inside: avoid;
orphans: 3;
widows: 2;
}
}
</style>`
// 打印組件
class ReactPrint extends React.Component {
constructor() {
super();
this.guid = getGuid();
this.inter = null;
}
// 創(chuàng)建iframe容器
createdIframe() {
// 約定iframe的id為#reactPrintIframe
let iframe = document.getElementById('reactPrintIframe');
if (!iframe) {
iframe = document.createElement('IFRAME');
iframe.setAttribute('id', 'reactPrintIframe');
// 讓iframe不可見(jiàn)
iframe.setAttribute('style', 'position:fixed;width:0px;height:0px;left:-3500px;top:-3500px;z-index:-1;margin:0;');
document.body.appendChild(iframe);
}
return iframe;
}
// 獲取需要打印的HTML
getPrintContent() {
return document.getElementById(this.guid).innerHTML;
}
// 獲取當(dāng)前內(nèi)容所在document的head, 并且過(guò)濾掉js
getParentHead() {
const head = document.head;
const childs = head.childNodes;
for (var i = 0; i < childs.length; i++) {
const child = childs[i];
if (child.nodeType === 1 && child.tagName.toLowerCase() === 'script') {
head.removeChild(child);
i--;
}
}
return head;
}
// 打印方法
print() {
if (this.inter) {
clearTimeout(this.inter);
}
const iframe = this.createdIframe();
const parentHead = this.getParentHead();
let doc = iframe.contentWindow.document || iframe.contentDocument.document;
doc.head.innerHTML = parentHead.innerHTML + style;
doc.body.innerHTML = this.getPrintContent();
doc.close();
// 延遲打印
this.inter = setTimeout(() => {
iframe.contentWindow.focus();
iframe.contentWindow.print();
}, 350);
}
render() {
return e(
'div', {
id: this.guid,
},
this.props.children
)
}
}
根據(jù)功能和開(kāi)頭遇到的三個(gè)問(wèn)題三椿,簡(jiǎn)單的講解一下每一塊代碼的所解決的問(wèn)題缺菌。
-
getGuid
主要是為了生成一個(gè)唯一的容器id, 用來(lái)獲取需要打印的DOM -
ReactPrint.createdIframe
很明顯就是用來(lái)創(chuàng)建一個(gè)iframe搜锰,我們約定了一個(gè)iframe的id伴郁,避免重復(fù)創(chuàng)建iframe造成的不必要內(nèi)存開(kāi)銷。 -
ReactPrint.getPrintContent
通過(guò)guid用來(lái)獲取需要打印的DOM -
ReactPrint.getParentHead
這個(gè)比較關(guān)鍵蛋叼,我們獲取當(dāng)前頁(yè)面的head焊傅,主要是為了獲取樣式相關(guān)的style、link載入到iframe中狈涮,這里方法里做了script
標(biāo)簽的過(guò)濾狐胎,用不到j(luò)s也沒(méi)有必要加載,直接移除避免沒(méi)有必要的異常情況歌馍。 -
ReactPrint.print
這個(gè)就核心的方法顽爹,進(jìn)行iframe的DOM替換和樣式動(dòng)態(tài)加載,這里為什么print的時(shí)候放在setTimeout中骆姐,主要就是為了解決資源沒(méi)有加載完畢就打印造成的樣式和圖片丟失問(wèn)題,如果資源加載過(guò)慢用setTimeout還是無(wú)法解決的捏题。