本文直擊兩個最常見的導出痛點,并給出可直接落地的診斷 + 修復方案(適用于 html2canvas + jsPDF + ECharts/自繪 canvas 場景)。
問題清單
- 問題 A:導出后圖表模糊,線條與文字不清晰(低分辨率)。
- 問題 B:開啟高分辨率導出后,圖表被放大或顯示不全(比例錯亂 / 裁切)。
針對每個問題:現象 → 根因 → 快速診斷 → 代碼層面修復要點。
問題 A:圖表模糊(低分辨率)
癥狀
- 導出的 PDF 上圖表看起來像被縮小拉伸,細線與文字失真、模糊。
根因(要點)
- 導出位圖分辨率不足:html2canvas / canvas 的 scale 過小,生成的位圖像素不足以滿足 A4 打印分辨率。
- 圖表在默認 CSS 大小下只渲染為屏幕分辨率(devicePixelRatio)而非目標打印分辨率。
快速診斷
- 打開導出生成的中間圖片(base64)查看其像素寬度(是否接近 A4@目標 DPI 的像素寬,例如 210mm@300DPI ≈ 2480px)。
- 在瀏覽器控制臺臨時運行:
const img = new Image(); img.src = '<base64>'; document.body.appendChild(img)
,并查看自然寬度/高度。
修復要點(代碼級)
- 以目標 DPI 計算 html2canvas 的 scale:
// 目標:A4 @ 300 DPI
const A4_PX_WIDTH = 210 * 300 / 25.4; // ≈ 2480
const scale = A4_PX_WIDTH / elementCSSWidth;
html2canvas(element, { scale });
- 對 ECharts 使用高像素比導出(避免讓 html2canvas 通過普通縮放拉伸矢量):
// ec 為 echarts 實例
const imgData = ec.getDataURL({ pixelRatio: scale, type: 'jpeg', backgroundColor: '#fff' });
// 用 <img> 臨時替換 DOM 中的圖表節點,保持 CSS 尺寸不變
- 對自定義 canvas(項目里的 LineCharts)用 “intrinsic -> target” 放大方式導出:
- 源像素為
canvas.width/height
(intrinsic buffer),顯示尺寸用clientWidth/clientHeight
。 - 目標像素為
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);
要點說明:讓圖表先以高像素渲染(或導出高像素位圖),然后再由 html2canvas 捕捉頁面。這樣可把矢量/高 DPI 繪制的細節固化到位圖中,避免最終 PDF 模糊。
問題 B:開啟高分辨率后圖表被放大或顯示不全(比例錯亂 / 裁切)
癥狀
- 在把 scale 提高后,圖表在 PDF 中看起來被放大、局部被裁切,或圖像比例與頁面顯示不一致。
根因(要點)
- CSS 顯示尺寸(clientWidth/clientHeight)與 canvas intrinsic 像素尺寸(canvas.width/height)混淆導致 drawImage 使用了錯誤的 source 或 dest 尺寸。Charts 實例通常會做
canvas.width = cssW * ratio
并ctx.scale(ratio)
。 - html2canvas 的合并 canvas(content canvas)尺?與隨后切片/插入 PDF 的換算不一致,造成裁切。
快速診斷
- 在控制臺打印涉及值:
- DOM 顯示寬度:
el.clientWidth
- canvas intrinsic:
canvas.width
- html2canvas 輸出 canvas:
contentCanvas.width
- DOM 顯示寬度:
- 若
contentCanvas.width
不等于clientWidth * scale
(預期),說明 scale 計算或替換流程有問題。
修復要點(代碼級)
- 在繪圖庫(
src/libs/charts/index.js
)中保證“清晰語義”:opts.grid.width/height
應基于dom.clientWidth/clientHeight
(CSS 顯示尺寸)。- canvas intrinsic 設置為
cssW * ratio
,并ctx.scale(ratio)
。
示例(已采納改動):
// 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);
- 導出時繪制步驟必須使用正確的 source/dest:
- sourceRect =
(0,0, canvas.width, canvas.height)
(intrinsic) - destRect =
(0,0, clientWidth * exportScale, clientHeight * exportScale)
這就避免了把 clientWidth
當作 source 或把 canvas.width
當作 dest 的錯誤。
- html2canvas 的 scale 應與上面 exportScale 一致,隨后把
contentCanvas
按相同換算切片成 A4 的像素高度并插入 PDF。
示例切片邏輯(核心):
const contentWidth = contentCanvas.width; // pixels
const pageHeight = (contentWidth / a4PtWidth) * a4PtHeight; // 保持比例
// 逐頁繪制:subCtx.drawImage(contentCanvas, 0, startY, contentWidth, subH, 0, 0, subCanvas.width, subCanvas.height);
要點說明:問題常因 “哪一個是 CSS 大小” 與 “哪一個是像素緩沖大小” 搞混而來。嚴格區分并使用 intrinsic → target 的繪制映射可徹底避免放大/裁切問題。
結論
- 模糊 = 分辨率不足 → 提高導出 scale / 先把圖表以高像素導出(ECharts
getDataURL({pixelRatio})
或自繪 canvas 放大)再捕獲。 - 放大/裁切 = 尺寸語義混淆 → 明確區分 CSS 顯示尺寸(clientWidth)與 canvas intrinsic(canvas.width),用 intrinsic 作為 source,用
clientWidth * scale
作為目標。
把以上兩點結合起來實現:優先把圖表固化為高分位圖,再以統一的 export-scale 用 html2canvas
捕獲并按 A4 切片插入 PDF,通常即可同時解決“模糊”與“比例錯亂”兩個問題。