紀錄工作經驗、相關知識,解決技術相關問題。

Javascript, React, 工作雜談, 新手 Jamp Holes, 網站相關, 資訊相關

[新手教學] JavaScript PDF

身為新手工程師,在實作前端網頁設計與互動功能時,經常會需要處理 PDF 檔案的各種操作,像是網頁內嵌預覽、動態列印或資料匯出。若沒有適當的處理工具,這些常見需求往往會耗費大量開發時間。在 JavaScript PDF 的技術領域中,有一個由社群與大廠廣泛使用的開源 PDF 庫——PDF.js,可以讓你在網頁上輕鬆整合 PDF 相關功能,實現流暢的無外掛閱讀體驗!

⚡ 快速跳轉目錄:

PDF.js 是什麼?

PDF.js 是由 Mozilla 團隊打造的 HTML5 JavaScript 庫,旨在提供能夠在網頁環境中呈現和操作 PDF 檔案的能力。它的運作原理是利用 HTML5 的 Canvas 將 PDF 內容精準呈現在網頁上,並提供多種 API 可供開發者呼叫,實現例如非同步頁面載入、動態縮放、列印等基本操作。

使用 PDF.js 的最大好處就是可以完全在瀏覽器端處理 PDF,免去使用者安裝額外瀏覽器外掛程式的麻煩。對於現代網頁應用來說,無縫整合 PDF 瀏覽體驗將變得輕而易舉。

官方連結:https://mozilla.github.io/pdf.js/

官方 Demo:https://mozilla.github.io/pdf.js/web/viewer.html

怎麼使用 PDF.js?

1. 引入庫檔案

首先,你需要在 HTML 引入 PDF.js 提供的 JS 檔案,官網上有 CDN 和 npm 安裝兩種選擇。舉例來說,可以直接在 <head> 引入 CDN 版本:

<script src="https://mozilla.github.io/pdf.js/build/pdf.js"></script>

如果是在現代前端應用開發中使用 PDF.js,建議透過 npm 套件管理器安裝:

首先確認已安裝 Node.js 和 npm,然後在專案根目錄執行:

npm install pdfjs-dist

安裝完成後,即可在 JavaScript 檔案中進行引用與 Worker 設定:

import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf';
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/build/pdf.worker.min.js';

2. 準備容器元素

接著在 HTML 中準備一個 <canvas> 容器,等一下 PDF 檔案的指定頁面內容就會被渲染在這個畫布上:

<canvas id="pdf-viewer"></canvas>

3. 撰寫 JavaScript 程式碼

最後就是撰寫 JavaScript 程式碼了。利用 PDF.js 提供的 API,從指定 PDF 檔案路徑開始載入並執行 Canvas 渲染:

const url = 'path/to/pdf.pdf';

pdfjsLib.getDocument(url).promise.then(doc => {
    return doc.getPage(1);
}).then(page => {
    const scale = 1.5;
    const viewport = page.getViewport({ scale: scale });
    const canvas = document.getElementById('pdf-viewer');
    const context = canvas.getContext('2d');
    
    canvas.height = viewport.height;
    canvas.width = viewport.width;

    const renderContext = {
        canvasContext: context,
        viewport: viewport
    };

    return page.render(renderContext).promise;
}).then(() => {
    console.log('PDF 頁面渲染完成!');
}).catch(error => {
    console.error('載入或渲染 PDF 時發生錯誤:', error);
});

上述範例完整示範瞭如何使用 PDF.js 庫載入 PDF 檔案的第一頁,並透過 Promise 鏈結確保非同步渲染流程正確執行,將內容呈現在畫布中。你可依實際業務需求調整縮放比例(scale)、頁數索引等參數。

4. Retina 螢幕高解析度與 Worker 跨域排錯

在實際將程式碼部署到生產環境時,開發者常會遇到兩個經典痛點:一是高畫素密度(Retina)螢幕上文字模糊,二是 Worker 載入失敗導致程式完全停擺。以下為對應的解決思路與修正寫法:

Retina 螢幕高解析度模糊修正

在高解析度螢幕(如 MacBook Retina 顯示器)上,直接賦值 viewport.height 與 viewport.width 會導致 Canvas 畫布以低畫素張開,渲染出的 PDF 文字與線條會出現鋸齒狀模糊。透過裝置像素比(window.devicePixelRatio)進行縮放修正,可讓 PDF 呈現極為清晰的向量字型效果:

const scale = 1.5;
const viewport = page.getViewport({ scale: scale });
const canvas = document.getElementById('pdf-viewer');
const context = canvas.getContext('2d');

// 取得裝置像素比,若無則預設為 1
const outputScale = window.devicePixelRatio || 1;

// 放大 Canvas 實際像素大小以適應 Retina 螢幕
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);

// 透過 CSS 限制畫布在網頁上的顯示大小
canvas.style.width = Math.floor(viewport.width) + 'px';
canvas.style.height = Math.floor(viewport.height) + 'px';

// 縮放 Context 座標系確保渲染比例正確
const transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;

const renderContext = {
    canvasContext: context,
    viewport: viewport,
    transform: transform
};

page.render(renderContext);

Worker 載入失敗與 CORS 跨域排錯提示

PDF.js 為了避免繁重的解析運算阻塞網頁主執行緒(Main Thread),會透過 Web Worker 在背景執行緒解析 PDF。如果在實際打包部署時發生 PDF 無法載入或控制台報錯,請檢查以下兩點:

  1. Worker 檔案路徑與跨域(CORS)限制:若將 pdf.worker.min.js 放置於外部 CDN,而該 CDN 未正確設定允許跨域存取(CORS Header),瀏覽器基於安全性考量會拒絕載入該 Worker。建議將 worker 檔案放置於同網域下,或確保 CDN 伺服器支援跨域回應。
  2. 現代前端打包工具(Webpack / Vite)設定:在使用 npm 套件時,若直接指向 node_modules 中的 worker 路徑,生產環境打包常會發生找不到檔案的 404 錯誤。建議透過靜態資源複製外掛,或是直接引入對應版本的 CDN 遠端 Worker 網址,確保路徑在編譯後依然正確有效。

除了基本渲染功能外,PDF.js 還提供了豐富的 API,可實現縮放、文字搜尋、列印、文字選取等多元操作。這些功能將大幅強化你的網頁應用,提供更佳的檔案整合體驗。

5. 實務開發常見踩坑與排錯防雷

在前端實務專案中,處理 PDF 檔案除了上述基礎渲染與 Retina 調整外,開發者還經常會遇到記憶體流失、中文字型亂碼或跨網域存取失敗等挑戰。以下整理三個最常遇到的真實踩坑情境與解法:

1. 記憶體流失與多頁切換時的 canvas 殘影(RenderingCancelledException)

  • 情境說明:當使用者在網頁上點擊「上一頁/下一頁」按鈕快速切換 PDF 頁數時,若未妥善處理上一個尚未完成的非同步渲染任務(Render Task),畫布會同時疊印好幾頁的內容,或直接噴出 RenderingCancelledException: Rendering has been cancelled 錯誤。
  • 解決思路:在每次呼叫 page.render() 之前,必須將上一次的 renderTask 實例保存起來,並在切換頁面或元件銷毀時主動呼叫 .cancel():
let currentRenderTask = null;

function renderPage(pageNumber) {
  pdfDoc.getPage(pageNumber).then(page => {
    // 若有正在執行的渲染任務,先取消它以避免殘影
    if (currentRenderTask) {
      currentRenderTask.cancel();
    }

    const viewport = page.getViewport({ scale: 1.5 });
    const canvas = document.getElementById('pdf-viewer');
    const context = canvas.getContext('2d');
    
    canvas.width = viewport.width;
    canvas.height = viewport.height;

    // 建立新的渲染任務並保存參照
    currentRenderTask = page.render({
      canvasContext: context,
      viewport: viewport
    });

    currentRenderTask.promise.then(() => {
      currentRenderTask = null; // 渲染完成後清空參照
    }).catch(error => {
      if (error.name === 'RenderingCancelledException') {
        console.log('前一次的渲染任務已被安全取消');
      } else {
        console.error('渲染失敗:', error);
      }
    });
  });
}

2. 中文字型渲染變成亂碼、方框(豆腐字)或無法選取

  • 情境說明:部分由公司內部系統或特定中文軟體輸出的 PDF 檔案,若其內嵌字型未被 PDF.js 標準支援,或者在 Canvas 模式下預設無法選取文字,使用者在前端會看到整片空白或亂碼方框。
  • 解決思路:
    • 字型遺失處理:確保在初始化 getDocument 時,有正確引入官方的 CMap 檔案路徑(cMapUrl 與 cMapPacked),否則特殊中文語系字型會無法正確解析:
      const loadingTask = pdfjsLib.getDocument({
        url: 'path/to/pdf.pdf',
        cMapUrl: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/{VERSION}/cmaps/',
        cMapPacked: true,
      });
    • 文字選取需求:若業務需求要求使用者必須能在畫布上用滑鼠選取、複製 PDF 內的文字,單純靠 <canvas> 是辦不到的。必須搭配 PDF.js 提供的 <div id="text-layer"> 容器與 TextLayerBuilder,在 Canvas 上方疊加一層透明的 DOM 文字選取層。

3. 跨網域(CORS)載入 PDF 檔案導致 Missing PDF 錯誤

  • 情境說明:前端網頁部署在網域 A,而 PDF 檔案存放在 AWS S3、GCP Storage 或另一台檔案伺服器網域 B。直接將 url 傳入 pdfjsLib.getDocument(url) 時,瀏覽器會直接攔截並噴出 CORS 錯誤,導致 PDF.js 誤判為檔案遺失(Missing PDF)。
  • 解決思路:
    • 確保檔案伺服器(如 S3 Bucket 的 CORS 設定)有正確開放 Access-Control-Allow-Origin: *(或指定前端網域)。
    • 若無法修改遠端伺服器的 Header,則必須透過後端(Node.js / Go / Python)寫一支 Proxy API,由後端向遠端抓取 PDF 檔案後,再將資料流(Stream)回傳給前端給 PDF.js 讀取。

總結

在網頁前端工程中,處理 PDF 檔案是相當常見的實際需求。有了 PDF.js 這個強大的 JavaScript PDF 工具,開發者便能擺脫對第三方外掛的依賴,在瀏覽器端完成高效能的渲染與互動。對於新手來說,熟悉這個工具的非同步載入機制、Canvas 繪製流程、Retina 螢幕的高解析度調整以及常見的踩坑排錯技巧,將是提升前端實作能力的一大利器。現在就動手測試看看,為你的專案打造流暢的 PDF 檢視功能吧!

參考資料與延伸閱讀

發佈留言