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