[轉(zhuǎn)帖]揭秘 html2Canvas:打印 PDF 導出的原理解析
當前位置:點晴教程→知識管理交流
→『 技術(shù)文檔交流 』
:揭秘 html2Canvas:打印 PDF 導出的原理解析 揭秘 html2Canvas:打印 PDF 導出的原理解析1. 前言最近我需要將網(wǎng)頁的DOM輸出為PDF文件,我使用的技術(shù)是html2Canvas和jsPDF。具體流程是,首先使用html2Canvas將DOM轉(zhuǎn)化為圖片,然后將圖片添加到j(luò)sPDF中進行輸出。 const pdf = new jsPDF({ unit: 'pt', format: 'a4', orientation: 'p', }); const canvas = await html2canvas(element, { onrendered: function (canvas) { document.body.appendChild(canvas); } } ); const canvasData = canvas.toDataURL('image/jpeg', 1.0); pdf.addImage(canvasData, 10, 10); pdf.save('jecyu.pdf'); 遇到了圖片導出模糊的問題,解決思路是:
scale: window\.devicePixelRatio \* 3, // 增加清晰度
// 獲取canavs轉(zhuǎn)化后的寬度 const canvasWidth = canvas.width; // 獲取canvas轉(zhuǎn)化后的高度 const canvasHeight = canvas.height; // 高度轉(zhuǎn)化為PDF的高度 const height = (width / canvasWidth) \* canvasHeight; // 1 比 1 進行縮放 pdf.addImage(data, 'JPEG', 0, 0, width, height); pdf.save('jecyu.pdf'); 要想了解為什么這樣設(shè)置打印出來的圖片變得更加清晰,需要先了解一些有關(guān)圖像的概念。 2. 一些概念2.1 英寸英寸是用來描述屏幕物理大小的單位,以對角線長度為度量標準。常見的例子有電腦顯示器的17英寸或22英寸,手機顯示器的4.8英寸或5.7英寸等。厘米和英寸的換算是1英寸等于2.54厘米。 2.2 像素像素是圖像顯示的基本單元,無法再分割。它是由單一顏色的小方格組成的。每個點陣圖都由若干像素組成,這些小方格的顏色和位置決定了圖像的樣子。 圖片、電子屏幕和打印機打印的紙張都是由許多特定顏色和位置的小方塊拼接而成的。一個像素通常被視為圖像的最小完整樣本,但它的定義和上下文有關(guān)。例如,我們可以在可見像素(打印出來的頁面圖像)、屏幕上的像素或數(shù)字相機的感光元素中使用像素。根據(jù)上下文,可以使用更精確的同義詞,如像素、采樣點、點或斑點。 2.3 PPI 與 DPIPPI (Pixel Per Inch):每英寸包括的像素數(shù),用來描述屏幕的像素密度。 DPI(Dot Per Inch):即每英寸包括的點數(shù)。 在這里,點是一個抽象的單位,可以是屏幕像素點、圖片像素點,也可以是打印的墨點。在描述圖片和屏幕時,通常會使用DPI,這等同于PPI。DPI最常用于描述打印機,表示每英寸打印的點數(shù)。一張圖片在屏幕上顯示時,像素點是規(guī)則排列的,每個像素點都有特定的位置和顏色。當使用打印機打印時,打印機可能不會規(guī)則地打印這些點,而是使用打印點來呈現(xiàn)圖像,這些打印點之間會有一定的空隙,這就是DPI所描述的:打印點的密度。 在這張圖片中,我們可以清晰地看到打印機是如何使用墨點打印圖像的。打印機的DPI越高,打印出的圖像就越精細,但同時也會消耗更多的墨點和時間。 2.4 設(shè)備像素設(shè)備像素(物理像素)dp:device pixels,顯示屏就是由一個個物理像素點組成,屏幕從工廠出來那天物理像素點就固定不變了,也就是我們經(jīng)常看到的手機分辨率所描述的數(shù)字。 一個像素并不一定是小正方形區(qū)塊,也沒有標準的寬高,只是用于豐富色彩的一個“點”而已。 2.5 屏幕分辨率屏幕分辨率是指一個屏幕由多少像素組成,常說的分辨率指的就是物理像素。手機屏幕的橫向和縱向像素點數(shù)以 px 為單位。 iPhone XS Max 和 iPhone SE 的屏幕分辨率分別為 2688x1242 和 1136x640。分辨率越高,屏幕上顯示的像素就越多,單個像素的尺寸也就越小,因此顯示效果更加精細。 2.6 圖片分辨率在我們所說的圖像分辨率中,指的是圖像中包含的像素數(shù)量。例如,一張圖像的分辨率為 800 x 400,這意味著圖像在垂直和水平方向上的像素點數(shù)分別為 800 和 400。圖像分辨率越高,圖像越清晰,但它也會受到顯示屏尺寸和分辨率的影響。 如果將 800 x 400 的圖像放大到 1600 x 800 的尺寸,它會比原始圖像模糊。通過圖像分辨率和顯示尺寸,可以計算出 dpi,這是圖像顯示質(zhì)量的指標。但它還會受到顯示屏影響,例如最高顯示屏 dpi 為 100,即使圖像 dpi 為 200,最高也只能顯示 100 的質(zhì)量。 可以通過 dpi 和顯示尺寸,計算出圖片原來的像素數(shù)。 這張照片的尺寸為 4x4 英寸,分辨率為 300 dpi,即每英寸有 300 個像素。因此它的實際像素數(shù)量是寬 1200 像素,高 1200 像素。如果有一張同樣尺寸(4x4 英寸)但分辨率為 72 dpi 的照片,那么它的像素數(shù)量就是寬 288 像素,高 288 像素。當你放大這兩張照片時,由于像素數(shù)量的差異,可能會導致細節(jié)的清晰度不同。 怎么計算 dpi 呢?dpi = 像素數(shù)量 / 尺寸 舉個例子說明: 假設(shè)您有一張寬為1200像素,高為800像素的圖片,您想將其打印成4x6英寸的尺寸。為此,您可以使用以下公式計算分辨率:寬度分辨率 = 1200像素/4英寸 = 300 dpi;高度分辨率 = 800像素/6英寸 = 133.33 dpi。因此,這張圖片的分辨率為300 dpi(寬度)和133.33 dpi(高度)。需要注意的是,計算得出的分辨率僅為參考值,實際的顯示效果還會受到顯示設(shè)備的限制。 同一尺寸的圖片,同一個設(shè)備下,圖片分辨率越高,圖片越清晰。 2.7 設(shè)備獨立像素前面我們說到顯示尺寸,可以使用 CSS 像素來描述圖片在顯示屏上的大小,而 CSS 像素就是設(shè)備獨立像素。設(shè)備獨立像素(邏輯像素)dip:device-independent pixels,獨立于設(shè)備的像素。也叫密度無關(guān)像素。 為什么會有設(shè)備獨立像素呢? 智能手機的發(fā)展非常迅速。幾年前,我們使用的手機分辨率非常低,例如左側(cè)的白色手機,它的分辨率只有320x480。但是,隨著科技的進步,低分辨率手機已經(jīng)無法滿足我們的需求了?,F(xiàn)在,我們有更高分辨率的屏幕,例如右側(cè)的黑色手機,它的分辨率是640x960,是白色手機的兩倍。因此,如果在這兩個手機上展示同一張照片,黑色手機上的每個像素點都對應(yīng)白色手機上的兩個像素點。 理論上,一個圖片像素對應(yīng)1個設(shè)備物理像素,圖片才能得到完美清晰的展示。因為黑色手機的分辨率更高,每英寸顯示的像素數(shù)量增多,縮放因素較大,所以圖片被縮小以適應(yīng)更高的像素密度。而在白色手機的分辨率較低,每英寸顯示的像素數(shù)量較少,縮放因素較小,所以圖片看起來相對較大。 為了解決分辨率越高的手機,頁面元素越來越小的問題,確保在白色手機和黑色手機看起來大小一致,就出現(xiàn)了設(shè)備獨立像素。它可以認為是計算機坐標系統(tǒng)中得到一個點,這個點代表可以由程序使用的虛擬像素。 例如,一個列表的寬度 300 個獨立像素,那么在白色手機會用 300個物理像素去渲染它,而黑色手機使用 600個物理像素去渲染它,它們大小是一致的,只是清晰度不同。 那么操作系統(tǒng)是怎么知道 300 個獨立像素,應(yīng)該用多少個物理像素去渲染它呢?這就涉及到設(shè)備像素比。 2.8 設(shè)備像素比設(shè)備像素比是指物理像素和設(shè)備獨立像素之間的比例關(guān)系,可以用devicePixelRatio來表示。具體而言,它可以按以下公式計算得出。 設(shè)備像素比:物理像素 / 設(shè)備獨立像素 // 在某一方向上,x 方向或者 y 方向 在Javascript中,可以使用window.devicePixelRatio獲取設(shè)備的DPR。設(shè)備像素比有兩個主要目的:
開發(fā)人員可以使用邏輯像素來布局和設(shè)計網(wǎng)頁或應(yīng)用程序,而不必考慮設(shè)備的物理像素。系統(tǒng)會根據(jù)設(shè)備像素比自動進行縮放和適配,以確保內(nèi)容的一致性和最佳顯示效果。 3. 分析原理3.1 html2canvas 整體流程在使用html2canvas時,有兩種可選的模式:一種是使用foreignObject,另一種是使用純canvas繪制。 使用第一種模式時,需要經(jīng)過以下步驟:首先將需要截圖的DOM元素進行克隆,并在過程中附上getComputedStyle的style屬性,然后將其放入SVG的foreignObject中,最后將SVG序列化成img的src(SVG直接內(nèi)聯(lián))。 img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(new XMLSerializer().serializeToString(svg)); 4.ctx.drawImage(img, ....) 第二種模式是使用純Canvas進行截圖的步驟。具體步驟如下:
3.2 分析畫布屬性 width、height、scale通常情況下,每個位圖像素應(yīng)該對應(yīng)一個物理像素,才能呈現(xiàn)完美清晰的圖片。但是在 retina 屏幕下,由于位圖像素點不足,圖片就會變得模糊。 為了確保在不同分辨率的屏幕下輸出的圖片清晰度與屏幕上顯示一致,該程序會取視圖的 假設(shè)在 dpr 為 1 的屏幕,假如這里 scale 傳入值為 2,那么寬、高和畫布上下文都乘以 2倍。 為什么要這樣做呢?因為在 canvas 中,默認情況下,一個單位恰好是一個像素,而縮放變換會改變這種默認行為。比如,縮放因子為 0.5 時,單位大小就變成了 0.5 像素,因此形狀會以正常尺寸的一半進行繪制;而縮放因子為 2.0 時,單位大小會增加,使一個單位變成兩個像素,形狀會以正常尺寸的兩倍進行繪制。 如下例子,通過放大倍數(shù)繪制,輸出一張含有更多像素的大圖 // 創(chuàng)建 Canvas 元素 const canvas = document.createElement('canvas'); canvas.width = 200; canvas.height = 200; // 獲取繪圖上下文 const ctx = canvas.getContext('2d'); // 繪制矩形 ctx.fillStyle = 'red'; ctx.fillRect(50, 50, 100, 100); document.body.appendChild(canvas) //== 放大2倍畫布 ==// const canvas2 = document.createElement('canvas'); // // 改變 Canvas 的 width 和 height canvas2.width = 400; canvas2.height = 400; const ctx2 = canvas2.getContext('2d'); // 繪制矩形 ctx2.scale(2, 2); // 將坐標系放大2倍,必須放置在繪制矩形前才生效 ctx2.fillStyle = 'blue'; ctx2.fillRect(50, 50, 100, 100); document.body.appendChild(canvas2) 3.3 為什么 使用 dpr * 倍數(shù)進行 scale在使用html2Canvas時,默認會根據(jù)設(shè)備像素比例(dpr)來輸出與屏幕上顯示的圖片清晰度相同的圖像。但是,如果需要打印更高分辨率的圖像,則需要將dpr乘以相應(yīng)的倍數(shù)。例如,如果我們想要將一張800像素寬,600像素高,72dpi分辨率的屏幕圖片打印在一張8x6英寸,300dpi分辨率的紙上,我們需要確保圖片像素與打印所需像素相同,以保證清晰度。 步驟 1: 將紙的尺寸轉(zhuǎn)換為像素 可以使用打印分辨率來確定轉(zhuǎn)換后的像素尺寸。 假設(shè)打印分辨率為 300 dpi,紙的尺寸為 8x6 英寸,那么: 紙的寬度像素 = 8 英寸 * 300 dpi = 2400 像素 紙的高度像素 = 6 英寸 * 300 dpi = 1800 像素 步驟 2: 計算圖片在紙上的實際尺寸 將圖片的尺寸與紙的尺寸進行比例縮放,以確定在紙上的實際打印尺寸 。 圖片在紙上的寬度 = (圖片寬度 / 屏幕像素每英寸) * 打印分辨率 圖片在紙上的高度 = (圖片高度 / 屏幕像素每英寸) * 打印分辨率 圖片在紙上的寬度 = (800 / 72) * 300 = 3333.33 像素(約為 3334 像素) 圖片在紙上的高度 = (600 / 72) * 300 = 2500 像素 步驟 3: 調(diào)整圖片大小和打印分辨率 根據(jù)計算出的實際尺寸,可以將圖片的大小調(diào)整為適合打印的尺寸,并設(shè)置適當?shù)拇蛴》直媛省?/p> 圖片在紙上的寬度為 3334 像素,高度為 2500 像素。 也就是說,在保持分辨率為 72 dpi 的情況下,需要把原來 800*600 的圖片,調(diào)整像素為 3334 * 2500。如果是位圖直接放大,就會變糊。如果是矢量圖,就不會有問題。這也是 html2Canvas 最終通過放大 scale 來提高打印清晰度的原因。 在圖片調(diào)整像素為 *3334 * 2500,雖然屏幕寬高變大了,但通過打印尺寸的換算,最終還是 6 8 英寸,分辨率 為 300dpi。 在本案例中,我們需要打印出一個可以正常查看的 pdf,對于 A4尺寸,我們可以用 pt 作為單位,其尺寸為 595pt * 841pt。 實際尺寸為 595/72 = 8.26英寸,841/72 = 11.68英寸。為了打印高清圖片,需要確保每英寸有300個像素,也就是8.26 * 300 = 2478像素,11.68 * 300 = 3504 像素,也就是說 canvas 轉(zhuǎn)出的圖片必須要這么大,最終打印的像素才這么清晰。 而在繪制 DOM 中,由于調(diào)試時不需要這么大,我們可以縮放比例,比如縮小至3倍,這樣圖片大小就為826像素 * 1168像素。如果高度超過1168像素,則需要考慮分頁打印。 下面是 pt 轉(zhuǎn)其他單位的計算公式 function convertPointsToUnit(points, unit) { // Unit table from <https://github.com/MrRio/jsPDF/blob/ddbfc0f0250ca908f8061a72fa057116b7613e78/jspdf.js#L791> var multiplier; switch(unit) { case 'pt': multiplier = 1; break; case 'mm': multiplier = 72 / 25.4; break; case 'cm': multiplier = 72 / 2.54; break; case 'in': multiplier = 72; break; case 'px': multiplier = 96 / 72; break; case 'pc': multiplier = 12; break; case 'em': multiplier = 12; break; case 'ex': multiplier = 6; break; default: throw ('Invalid unit: ' + unit); } return points \* multiplier; } 4. 擴展4.1 為什么使用大圖片 Icon 打印出來還模糊在理論上,一個位圖像素應(yīng)該對應(yīng)一個物理像素,這樣圖片才能完美清晰地展示。在普通屏幕上,這沒有問題,但在Retina屏幕上,由于位圖像素點不足,圖片會變得模糊。 所以,對于圖片高清問題,比較好的方案是 如:200x300(css pixel)img標簽,就需要提供 400x600 的圖片。 如此一來,位圖像素點個數(shù)就是原來的 4 倍,在 retina 屏幕下,位圖像素個數(shù)就可以跟物理像素個數(shù) 形成 這里還有另一個問題,如果普通屏幕下,也用了 很明顯,在普通屏幕下(dpr1),200X300(css pixel)img 標簽,所對應(yīng)的物理像素個數(shù)就是 我們稱這個過程叫做(downsampling),肉眼看上去雖然圖片不會模糊,但是會覺得圖片缺失一些銳利度。 通常在做移動端開發(fā)時,對于沒那么精致的app,統(tǒng)一使用 @2x 就好了。 上面 100x100的圖片,分別放在 100x100、50x50、25x25的 img 容器中,在 retina 屏幕下的顯示效果 條碼圖,通過放大鏡其實可以看出邊界像素點取值的不同:
要想大的位圖 icon 縮小時保證顯示質(zhì)量,那就需要這樣設(shè)置: img { image-rendering:-moz-crisp-edges; image-rendering:-o-crisp-edges; image-rendering:-webkit-optimize-contrast; image-rendering: crisp-edges; -ms-interpolation-mode: nearest-neighbor; -webkit-font-smooting: antialiased; } 5. 總結(jié)本文介紹了如何通過使用 html2Canvas 來打印高清圖片,并解釋了一些與圖片有關(guān)的術(shù)語,包括英寸、像素、PPI 與 DPI、設(shè)備像素、分辨率等,同時逐步分析了 html2Canvas 打印高清圖片的原理。 該文章在 2023/10/28 12:01:46 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |