小王盯著屏幕,感覺(jué)頭發(fā)又要掉幾根。
“王哥,又在跟打印、導(dǎo)出 PDF 較勁呢?”,剛?cè)肼毜男±疃酥Х?,幸?zāi)樂(lè)禍地問(wèn)。
“別提了!甲方爸爸這次要求更變態(tài),不僅要指定區(qū)域打印、導(dǎo)出 PDF、在線(xiàn)預(yù)覽,還要保證和頁(yè)面上的 DOM 元素一模一樣!”,小王揉了揉太陽(yáng)穴,“這簡(jiǎn)直是逼我用 html2canvas + jsPDF
搞像素魔法
??!”
“王哥,這套路我都熟透了!”,小李喝了口咖啡,慢悠悠地說(shuō),“先用 html2canvas 把指定的 DOM 元素轉(zhuǎn)換成 canvas,然后用 jsPDF 的 addImage 方法把 canvas 生成的圖片添加到 PDF 里,最后根據(jù) type 參數(shù)判斷是下載、打印還是預(yù)覽,對(duì)吧?”
“沒(méi)錯(cuò),流程就是這么個(gè)流程”,小王無(wú)奈地嘆了口氣,“但問(wèn)題是,理想很豐滿(mǎn),現(xiàn)實(shí)很骨感!html2canvas 生成的 canvas,要么模糊得像打了馬賽克,要么就是顏色失真;jsPDF 的 addImage 方法更坑,位置、大小、比例,稍微一不對(duì),整個(gè) PDF 就亂成一鍋粥!這哪是像素魔法
,簡(jiǎn)直是像素災(zāi)難
!”
“王哥,你說(shuō)的這些坑,我都踩過(guò)!”,一直默默研究的小張突然抬起頭,推了推眼鏡,“我最近發(fā)現(xiàn),想要用 html2canvas + jsPDF 完美‘復(fù)刻’ DOM,關(guān)鍵在于細(xì)節(jié)!”
html2canvas:配置是關(guān)鍵,細(xì)節(jié)決定成敗
“首先,html2canvas 的配置至關(guān)重要!”,小張開(kāi)始講解,“useCORS: true
必須加上,否則跨域圖片直接 GG。scale
參數(shù)也不能亂用,要根據(jù)實(shí)際情況調(diào)整,才能保證 canvas 的清晰度。如果 DOM 元素里有復(fù)雜的 CSS 樣式,比如 box-shadow
、border-radius
還要開(kāi)啟 foreignObjectRendering: true
,才能正確渲染?!?/span>
jsPDF:精雕細(xì)琢,方能成就完美
“然后,jsPDF的addImage 方法也要精雕細(xì)琢!”,小張繼續(xù)說(shuō)道,“addImage
的坐標(biāo)和寬高,必須和 canvas 的實(shí)際尺寸對(duì)應(yīng),否則就會(huì)出現(xiàn)拉伸、變形。如果 PDF 需要分頁(yè),還要計(jì)算好每頁(yè)的高度,避免內(nèi)容被截?cái)??!?/span>
性能優(yōu)化:讓“像素魔法”飛起來(lái)
“最后,性能優(yōu)化也不能忽視!”,小張補(bǔ)充道,“html2canvas 轉(zhuǎn)換 DOM 的時(shí)候,會(huì)遍歷整個(gè) DOM 樹(shù),非常耗時(shí)??梢試L試使用 ignoreElements
選項(xiàng),忽略一些不必要的元素,或者使用 onclone
鉤子,在轉(zhuǎn)換之前對(duì) DOM 進(jìn)行一些預(yù)處理,減少轉(zhuǎn)換的復(fù)雜度?!?/span>
“張哥,聽(tīng)君一席話(huà),勝讀十年書(shū)??!”,小李佩服得五體投地。
另辟蹊徑:Print-JS,另一種選擇
“其實(shí),除了 html2canvas + jsPDF,還有一些其他的選擇”,小王沉吟道,“比如 Print-JS
,它可以直接打印指定的 DOM 元素,而且樣式也比較接近原始頁(yè)面。但是Print-JS 對(duì)于一些復(fù)雜的布局,可能無(wú)法完美支持?!?/span>
總結(jié):沒(méi)有銀彈,只有最合適的方案
“所以,我們要根據(jù)實(shí)際情況,選擇最合適的方案”,小王總結(jié)道,“對(duì)于簡(jiǎn)單的頁(yè)面,Print-JS 可能更便捷;對(duì)于需要高度還原的復(fù)雜頁(yè)面,html2canvas + jsPDF 才是王道。關(guān)鍵是要掌握各種像素魔法
,才能將 DOM 完美復(fù)刻
到 PDF!”
相信各位看官已經(jīng)對(duì) html2canvas + jsPDF 這套方案有了更深入的了解。那么,useCORS
、scale
、foreignObjectRendering
這些 html2canvas 的配置參數(shù)該如何設(shè)置?jsPDF 的 addImage
方法又有哪些技巧?
接下來(lái),就讓我們一起深入研究這些問(wèn)題,看看如何用 html2canvas + jsPDF 這套像素魔法
,將 DOM 完美“復(fù)刻”到 PDF,實(shí)現(xiàn)打印、導(dǎo)出、預(yù)覽pdf的終極目標(biāo)!
1.安裝依賴(lài)
html2canvas[1]:1.4.1
jspdf[2]:2.5.1
print-js[3]:1.6.0
npm install html2canvas jspdf print-js --save
2. 創(chuàng)建utils.js
文件
import print from 'print-js';
import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';
const htmlToPdf = ({
id, // 需要轉(zhuǎn)為pdf的html容器的id 必填
title, // download下載時(shí)文件的名稱(chēng) 選填
type, // 類(lèi)型 download 下載,print 打印,preview 預(yù)覽
canvasParams, // 畫(huà)布(html2Canvas)的相關(guān)參數(shù)
printParams, // 打?。╬rint-js)的相關(guān)參數(shù)
pdfPageCallback // pdf的分頁(yè)的自定義方法,處理完分頁(yè)需要把pdf對(duì)象返回 選填
}) => {
return new Promise((resolve, reject) => {
html2Canvas(document.querySelector(`#${id}`),{
taintTest: false, // 在渲染前測(cè)試圖片
background: "#fff",
useCORS: true, // 開(kāi)啟跨域配置
foreignObjectRendering: true, //
scale: window.devicePixelRatio
}, (canvasParams || {})).then((canvas) => {
let PDF = new JsPDF('', 'pt', 'a4');
if(typeof pdfPageCallback === 'function') {
PDF = pdfPageCallback(canvas, JsPDF);
} else {
// 內(nèi)容的寬度
let contentWidth = canvas.width;
// 內(nèi)容高度
let contentHeight = canvas.height;
// 一頁(yè)pdf顯示html頁(yè)面生成的canvas高度,a4紙的尺寸[595.28,841.89];
let pageHeight = (contentWidth / 592.28) * 841.89;
// 未生成pdf的html頁(yè)面高度
let leftHeight = contentHeight;
// 頁(yè)面偏移
let position = 0;
//a4紙的尺寸[595.28,841.89],html頁(yè)面生成的canvas在pdf中圖片的寬高
let imgWidth = 595.28;
let imgHeight = (592.28 / contentWidth) * contentHeight;
// canvas轉(zhuǎn)圖片數(shù)據(jù)
let pageData = canvas.toDataURL('image/jpeg', 1.0);
// 判斷是否需要分頁(yè)
if (leftHeight < pageHeight) {
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
} else {
while (leftHeight > 0) {
PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
leftHeight -= pageHeight;
position -= 841.89;
if (leftHeight > 0) {
// 新增一頁(yè)
PDF.addPage();
}
}
}
}
const blob = PDF.output('blob');
const fileURL = URL.createObjectURL(blob);
switch (type) {
case 'download':
PDF.save(`${title || new Date().getTime()}.pdf`);
break;
case 'print':
printJS({
printable: fileURL,
type: 'pdf',
header: null,
},(printParams || {})));
break;
case 'preview':
window.open(fileURL, '_bank');
break;
default:
break;
}
resolve({ blob, fileURL });
}).catch(err => {
reject(err);
});
});
};
export default htmlToPdf;
3.具體使用
import htmlToPdf from '@/utils.js'
// 下載pdf
const data = {
id:'container',
type:'download',
title:'PDF下載'
}
// 打印pdf
const data = {
id:'container',
type:'print',
}
// 預(yù)覽pdf
const data = {
id:'container',
type:'preview',
}
htmlToPdf(data);