使用JS渲染頁(yè)面并導(dǎo)出為PDF文件的常見(jiàn)問(wèn)題與修復(fù)
本文直擊兩個(gè)最常見(jiàn)的導(dǎo)出痛點(diǎn),并給出可直接落地的診斷 + 修復(fù)方案(適用于 html2canvas + jsPDF + ECharts/自繪 canvas 場(chǎng)景)。
問(wèn)題清單
- 問(wèn)題 A:導(dǎo)出后圖表模糊,線條與文字不清晰(低分辨率)。
- 問(wèn)題 B:開(kāi)啟高分辨率導(dǎo)出后,圖表被放大或顯示不全(比例錯(cuò)亂 / 裁切)。
針對(duì)每個(gè)問(wèn)題:現(xiàn)象 → 根因 → 快速診斷 → 代碼層面修復(fù)要點(diǎn)。
問(wèn)題 A:圖表模糊(低分辨率)
癥狀
- 導(dǎo)出的 PDF 上圖表看起來(lái)像被縮小拉伸,細(xì)線與文字失真、模糊。
根因(要點(diǎn))
- 導(dǎo)出位圖分辨率不足:html2canvas / canvas 的 scale 過(guò)小,生成的位圖像素不足以滿足 A4 打印分辨率。
- 圖表在默認(rèn) CSS 大小下只渲染為屏幕分辨率(devicePixelRatio)而非目標(biāo)打印分辨率。
快速診斷
- 打開(kāi)導(dǎo)出生成的中間圖片(base64)查看其像素寬度(是否接近 A4@目標(biāo) DPI 的像素寬,例如 210mm@300DPI ≈ 2480px)。
- 在瀏覽器控制臺(tái)臨時(shí)運(yùn)行:
const img = new Image(); img.src = '<base64>'; document.body.appendChild(img),并查看自然寬度/高度。
修復(fù)要點(diǎn)(代碼級(jí))
- 以目標(biāo) DPI 計(jì)算 html2canvas 的 scale:
// 目標(biāo):A4 @ 300 DPI
const A4_PX_WIDTH = 210 * 300 / 25.4; // ≈ 2480
const scale = A4_PX_WIDTH / elementCSSWidth;
html2canvas(element, { scale });
- 對(duì) ECharts 使用高像素比導(dǎo)出(避免讓 html2canvas 通過(guò)普通縮放拉伸矢量):
// ec 為 echarts 實(shí)例
const imgData = ec.getDataURL({ pixelRatio: scale, type: 'jpeg', backgroundColor: '#fff' });
// 用 <img> 臨時(shí)替換 DOM 中的圖表節(jié)點(diǎn),保持 CSS 尺寸不變
- 對(duì)自定義 canvas(項(xiàng)目里的 LineCharts)用 “intrinsic -> target” 放大方式導(dǎo)出:
- 源像素為
canvas.width/height(intrinsic buffer),顯示尺寸用clientWidth/clientHeight。 - 目標(biāo)像素為
clientWidth * scale。
const srcW = canvas.width, srcH = canvas.height; // intrinsic
const dstW = Math.round(canvas.clientWidth * scale);
const dstH = Math.round(canvas.clientHeight * scale);
const tmp = document.createElement('canvas');
tmp.width = dstW; tmp.height = dstH;
const tctx = tmp.getContext('2d');
// 可先填白:tctx.fillStyle='#fff'; tctx.fillRect(0,0,dstW,dstH);
tctx.drawImage(canvas, 0, 0, srcW, srcH, 0, 0, dstW, dstH);
const dataUrl = tmp.toDataURL('image/jpeg', 1.0);
要點(diǎn)說(shuō)明:讓圖表先以高像素渲染(或?qū)С龈呦袼匚粓D),然后再由 html2canvas 捕捉頁(yè)面。這樣可把矢量/高 DPI 繪制的細(xì)節(jié)固化到位圖中,避免最終 PDF 模糊。
問(wèn)題 B:開(kāi)啟高分辨率后圖表被放大或顯示不全(比例錯(cuò)亂 / 裁切)
癥狀
- 在把 scale 提高后,圖表在 PDF 中看起來(lái)被放大、局部被裁切,或圖像比例與頁(yè)面顯示不一致。
根因(要點(diǎn))
- CSS 顯示尺寸(clientWidth/clientHeight)與 canvas intrinsic 像素尺寸(canvas.width/height)混淆導(dǎo)致 drawImage 使用了錯(cuò)誤的 source 或 dest 尺寸。Charts 實(shí)例通常會(huì)做
canvas.width = cssW * ratio并ctx.scale(ratio)。 - html2canvas 的合并 canvas(content canvas)尺?與隨后切片/插入 PDF 的換算不一致,造成裁切。
快速診斷
- 在控制臺(tái)打印涉及值:
- DOM 顯示寬度:
el.clientWidth - canvas intrinsic:
canvas.width - html2canvas 輸出 canvas:
contentCanvas.width
- DOM 顯示寬度:
- 若
contentCanvas.width不等于clientWidth * scale(預(yù)期),說(shuō)明 scale 計(jì)算或替換流程有問(wèn)題。
修復(fù)要點(diǎn)(代碼級(jí))
- 在繪圖庫(kù)(
src/libs/charts/index.js)中保證“清晰語(yǔ)義”:opts.grid.width/height應(yīng)基于dom.clientWidth/clientHeight(CSS 顯示尺寸)。- canvas intrinsic 設(shè)置為
cssW * ratio,并ctx.scale(ratio)。
示例(已采納改動(dòng)):
// index.js (Charts.setOption) 建議 opts.grid.width = opts.grid.width || this.dom.clientWidth || this.dom.scrollWidth; opts.grid.height = opts.grid.height || this.dom.clientHeight || this.dom.scrollHeight; this.canvas.width = opts.grid.width * this.ratio; this.canvas.height = opts.grid.height * this.ratio; this.canvas.style.width = opts.grid.width + 'px'; this.canvas.style.height = opts.grid.height + 'px'; this.ctx.scale(this.ratio, this.ratio);
- 導(dǎo)出時(shí)繪制步驟必須使用正確的 source/dest:
- sourceRect =
(0,0, canvas.width, canvas.height)(intrinsic) - destRect =
(0,0, clientWidth * exportScale, clientHeight * exportScale)
這就避免了把 clientWidth 當(dāng)作 source 或把 canvas.width 當(dāng)作 dest 的錯(cuò)誤。
- html2canvas 的 scale 應(yīng)與上面 exportScale 一致,隨后把
contentCanvas按相同換算切片成 A4 的像素高度并插入 PDF。
示例切片邏輯(核心):
const contentWidth = contentCanvas.width; // pixels const pageHeight = (contentWidth / a4PtWidth) * a4PtHeight; // 保持比例 // 逐頁(yè)繪制:subCtx.drawImage(contentCanvas, 0, startY, contentWidth, subH, 0, 0, subCanvas.width, subCanvas.height);
要點(diǎn)說(shuō)明:?jiǎn)栴}常因 “哪一個(gè)是 CSS 大小” 與 “哪一個(gè)是像素緩沖大小” 搞混而來(lái)。嚴(yán)格區(qū)分并使用 intrinsic → target 的繪制映射可徹底避免放大/裁切問(wèn)題。
結(jié)論
- 模糊 = 分辨率不足 → 提高導(dǎo)出 scale / 先把圖表以高像素導(dǎo)出(ECharts
getDataURL({pixelRatio})或自繪 canvas 放大)再捕獲。 - 放大/裁切 = 尺寸語(yǔ)義混淆 → 明確區(qū)分 CSS 顯示尺寸(clientWidth)與 canvas intrinsic(canvas.width),用 intrinsic 作為 source,用
clientWidth * scale作為目標(biāo)。
把以上兩點(diǎn)結(jié)合起來(lái)實(shí)現(xiàn):優(yōu)先把圖表固化為高分位圖,再以統(tǒng)一的 export-scale 用 html2canvas 捕獲并按 A4 切片插入 PDF,通常即可同時(shí)解決“模糊”與“比例錯(cuò)亂”兩個(gè)問(wèn)題。
總結(jié)
到此這篇關(guān)于使用JS渲染頁(yè)面并導(dǎo)出為PDF文件的常見(jiàn)問(wèn)題與修復(fù)的文章就介紹到這了,更多相關(guān)JS渲染頁(yè)面并導(dǎo)出PDF內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS逆向之如何一招徹底解決selenium?WebDriver無(wú)響應(yīng)
Selenium Webdriver是一款強(qiáng)大的自動(dòng)化測(cè)試工具,用于模擬真實(shí)用戶在瀏覽器中的交互行為,下面這篇文章主要給大家介紹了關(guān)于JS逆向之如何一招徹底解決selenium?WebDriver無(wú)響應(yīng)的相關(guān)資料,需要的朋友可以參考下2024-07-07
JS如何將數(shù)字類型轉(zhuǎn)化為沒(méi)3個(gè)一個(gè)逗號(hào)的金錢格式
本文為大家介紹下如何將數(shù)字類型轉(zhuǎn)化為沒(méi)3個(gè)一個(gè)逗號(hào)的金錢格式,下面是具體的實(shí)現(xiàn)2014-01-01
webpack項(xiàng)目使用eslint建立代碼規(guī)范實(shí)現(xiàn)
這篇文章主要介紹了webpack項(xiàng)目使用eslint建立代碼規(guī)范實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
如何解決Webview和H5緩存問(wèn)題確保每次加載最新版本的資源詳解
WebView在加載H5頁(yè)面時(shí),可能會(huì)因?yàn)榫彺鏅C(jī)制導(dǎo)致用戶看到舊版本的頁(yè)面,這篇文章主要介紹了如何解決Webview和H5緩存問(wèn)題確保每次加載最新版本的資源的相關(guān)資料,需要的朋友可以參考下2025-02-02
JavaScript?評(píng)測(cè)代碼運(yùn)行速度的案例代碼
在?JavaScript?中,可以使用?performance.now()?API?來(lái)評(píng)測(cè)代碼的運(yùn)行速度。該?API?返回當(dāng)前頁(yè)面的高精度時(shí)間戳,您可以在代碼執(zhí)行前后調(diào)用它來(lái)計(jì)算代碼執(zhí)行所需的時(shí)間,這篇文章主要介紹了JavaScript?評(píng)測(cè)代碼運(yùn)行速度,需要的朋友可以參考下2023-02-02
IE11下CKEditor在Bootstrap Modal中下拉問(wèn)題的解決
這篇文章主要介紹了IE11下CKEditor在Bootstrap Modal中下拉問(wèn)題的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09
javascript下過(guò)濾數(shù)組重復(fù)值的代碼
javascript下過(guò)濾數(shù)組重復(fù)值的代碼...2007-09-09

