博客 / 詳情

返回

基於PDF.js的安全PDF預覽組件實現:從虛擬滾動到水印渲染

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

基於PDF.js的安全PDF預覽組件實現:從虛擬滾動到水印渲染

本文將詳細介紹如何基於Mozilla PDF.js實現一個功能完善、安全可靠的PDF預覽組件,重點講解虛擬滾動、雙模式渲染、水印實現等核心技術。

前言

在Web應用中實現PDF預覽功能是常見需求,尤其是在線教育、文檔管理等場景。然而,簡單的PDF預覽往往無法滿足實際業務需求,特別是在安全性方面。本文將介紹如何基於PDF.js實現一個功能完善的PDF預覽組件,並重點講解如何添加自定義防下載和水印功能,為文檔安全提供保障。

功能概覽

我們的PDF預覽組件實現了以下核心功能:

  1. 基礎功能:PDF文件加載與渲染、自定義尺寸控制、頁面縮放規則配置、主題切換
  2. 安全增強:動態水印添加、防下載功能、右鍵菜單禁用、打印控制
  3. 用户體驗:頁面渲染事件通知、響應式佈局適配、加載狀態反饋

技術實現

1. 虛擬滾動加載

對於大型PDF文件,一次性渲染所有頁面會導致嚴重的性能問題。我們通過虛擬滾動技術優化大文檔的加載性能,只渲染當前可見區域和附近的頁面:

// 頁面緩存管理
class PDFPageViewBuffer {
  #buf = new Set();
  #size = 0;

  constructor(size) {
    this.#size = size;  // 緩存頁面數量限制
  }

  push(view) {
    const buf = this.#buf;
    if (buf.has(view)) {
      buf.delete(view);
    }
    buf.add(view);
    if (buf.size > this.#size) {
      this.#destroyFirstView();  // 超出限制時銷燬最早的頁面
    }
  }
}

優勢:

  • 內存優化:只保留有限數量的頁面在內存中
  • 性能提升:減少不必要的渲染操作
  • 流暢體驗:滾動時動態加載頁面

2. 雙模式渲染:Canvas與HTML

PDF.js支持兩種渲染模式,可根據不同需求選擇。兩種渲染方式在視覺效果和性能上有明顯差異:

ScreenShot_2026-02-10_112550_802

 圖:HTML渲染模式下的PDF顯示效果

ScreenShot_2026-02-10_112559_413

 

圖:Canvas渲染模式下的PDF顯示效果

Canvas渲染(默認)
// 創建Canvas元素
const canvas = document.createElement("canvas");
canvas.setAttribute("role", "presentation");

// 獲取2D渲染上下文
const ctx = canvas.getContext("2d", {
  alpha: false,           // 禁用透明度通道,提高性能
  willReadFrequently: !this.#enableHWA  // 根據硬件加速設置優化
});

// 渲染PDF頁面到Canvas
const renderContext = {
  canvasContext: ctx,
  transform,
  viewport,
  // 其他參數...
};
const renderTask = pdfPage.render(renderContext);
HTML渲染
// HTML渲染模式(文本層)
if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE) {
  this.textLayer = new TextLayerBuilder({
    pdfPage,
    highlighter: this._textHighlighter,
    accessibilityManager: this._accessibilityManager,
    enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
    onAppend: (textLayerDiv) => {
      this.#addLayer(textLayerDiv, "textLayer");
    }
  });
}

兩種模式對比:

ScreenShot_2026-02-10_112610_375

 

3. 水印渲染實現

水印是保護文檔版權的重要手段。我們在PDF頁面渲染完成後,直接在Canvas上添加水印,確保水印與內容融為一體:

// 在渲染完成後添加水印
const resultPromise = renderTask.promise.then(async () => {
  showCanvas?.(true);
  await this.#finishRenderTask(renderTask);

  // 添加水印
  createWaterMark({ fontText: warterMark, canvas, ctx });

  // 其他處理...
});

// 水印繪製函數
function createWaterMark({
  ctx,
  canvas,
  fontText = '默認水印',
  fontFamily = 'microsoft yahei',
  fontSize = 30,
  fontcolor = 'rgba(218, 218, 218, 0.5)',
  rotate = 30,
  textAlign = 'left'
}) {
  // 保存當前狀態
  ctx.save();

  // 計算響應式字體大小
  const canvasW = canvas.width;
  const calfontSize = (fontSize * canvasW) / 800;
  ctx.font = `${calfontSize}px ${fontFamily}`;
  ctx.fillStyle = fontcolor;
  ctx.textAlign = textAlign;
  ctx.textBaseline = 'Middle';

  // 添加多個水印
  const pH = canvas.height / 4;
  const pW = canvas.width / 4;
  const positions = [
    { x: pW, y: pH },
    { x: 3 * pW, y: pH },
    { x: pW * 1.3, y: 3 * pH },
    { x: 3 * pW, y: 3 * pH }
  ];

  positions.forEach((pos) => {
    ctx.save();
    ctx.translate(pos.x, pos.y);
    ctx.rotate(-rotate * Math.PI / 180);
    ctx.fillText(fontText, 0, 0);
    ctx.restore();
  });

  // 恢復狀態
  ctx.restore();
}

水印技術亮點

  • 響應式設計:根據Canvas寬度自動調整水印尺寸
  • 多點佈局:四個位置分佈水印,覆蓋整個頁面
  • 旋轉效果:每個水印獨立旋轉30度,增加覆蓋範圍
  • 透明度處理:使用半透明顏色,不影響內容可讀性

4. 防下載與打印控制

為了增強文檔安全性,我們實現了全面的防下載和打印控制功能:

// 禁用右鍵菜單
document.addEventListener('contextmenu', function(e) {
  e.preventDefault();
  return false;
});

// 禁用文本選擇
document.addEventListener('selectstart', function(e) {
  e.preventDefault();
  return false;
});

// 禁用拖拽
document.addEventListener('dragstart', function(e) {
  e.preventDefault();
  return false;
});

// 攔截Ctrl+P打印快捷鍵
window.addEventListener("keydown", function (event) {
  if (event.keyCode === 80 && (event.ctrlKey || event.metaKey) && 
      !event.altKey && (!event.shiftKey || window.chrome || window.opera)) {
    // 自定義打印行為或完全禁用
    event.preventDefault();
    event.stopImmediatePropagation();
  }
}, true);

Vue組件實現

基於以上技術,我們實現了一個功能完善的Vue3 PDF預覽組件:

<template>
  <iframe
    :width="viewerWidth"
    :height="viewerHeight"
    id="ifra"
    frameborder="0"
    :src="`/pdfJs/web/viewer.html?file=${src}&waterMark=${waterMark}`"
    @load="pagesRendered"
  />
</template>

<script setup>
import { computed } from 'vue'
import { useUserStore } from '~/store/user'

const props = defineProps({
  src: String,
  width: [String, Number],
  height: [String, Number],
  pageScale: [String, Number],
  theme: String,
  fileName: String
})

const emit = defineEmits(['loaded'])

// 默認值設置
const propsWithDefaults = withDefaults(props, {
  width: '100%',
  height: '100vh',
  pageScale: 'page-width',
  theme: 'dark',
  fileName: ''
})

// 尺寸計算
const viewerWidth = computed(() => {
  if (typeof props.width === 'number') {
    return props.width + 'px'
  } else {
    return props.width
  }
})

const viewerHeight = computed(() => {
  if (typeof props.height === 'number') {
    return props.height + 'px'
  } else {
    return props.height
  }
})

// 用户信息和水印
const userStore = useUserStore()
const userInfo = computed(() => userStore.userInfo)

const waterMark = computed(() => {
  const { userName, phoneNum } = userInfo.value
  const phoneSuffix = phoneNum && phoneNum.substring(phoneNum.length - 4)
  return userName + phoneSuffix
})

// 頁面渲染事件
function pagesRendered(pdfApp) {
  emit('loaded', pdfApp)
}
</script>

<style scoped>
#ifra {
  max-width: 100%;
  height: 100%;
  margin-left: 50%;
  transform: translateX(-50%);
}
</style>

使用方法

基本使用

<template>
  <PDFViewer
    src="path/to/your/pdf/file.pdf"
    :width="800"
    :height="600"
    @loaded="handlePdfLoaded"
  />
</template>

<script setup>
import PDFViewer from '@/components/PDFViewer/index.vue'

function handlePdfLoaded(pdfApp) {
  console.log('PDF已加載完成', pdfApp)
}
</script>

高級配置

<template>
  <PDFViewer
    src="path/to/your/pdf/file.pdf"
    width="100%"
    height="90vh"
    page-scale="page-fit"
    theme="light"
    file-name="自定義文件名.pdf"
    @loaded="handlePdfLoaded"
  />
</template>

性能優化

1. 渲染性能優化

// 設置合理的maxCanvasPixels
const maxCanvasPixels = isHighEndDevice ? 
  16777216 * 4 :  // 4K顯示器
  8388608 * 2;   // 普通顯示器

const pdfViewer = new PDFViewer({
  container: document.getElementById('viewer'),
  maxCanvasPixels: maxCanvasPixels
});

2. 內存管理優化

// 限制緩存頁面數量,防止內存溢出
pdfViewer.setDocument(pdfDocument);
pdfViewer.currentScaleValue = 'auto';

// 定期清理不可見頁面
setInterval(() => {
  const visiblePages = pdfViewer._getVisiblePages();
  // 清理不可見頁面的緩存
}, 30000);

3. 按需渲染

// 只渲染可見頁面
pdfViewer.onPagesLoaded = () => {
  const visiblePages = pdfViewer._getVisiblePages();
  // 只渲染可見頁面,延遲渲染其他頁面
};

注意事項

  1. PDF.js版本:確保使用兼容的PDF.js版本,不同版本API可能有差異
  2. 跨域處理:PDF文件可能存在跨域問題,需確保服務器配置了正確的CORS頭
  3. 大文件處理:對於大型PDF文件,考慮添加加載進度提示
  4. 移動端適配:在移動設備上可能需要額外的樣式調整
  5. 安全限制:雖然實現了防下載和水印,但無法完全防止技術用户獲取PDF內容

擴展功能建議

  1. 頁面跳轉:添加頁面導航功能,支持直接跳轉到指定頁面
  2. 文本搜索:實現PDF內容搜索功能
  3. 註釋工具:添加PDF註釋、標記功能
  4. 水印樣式自定義:支持更多水印樣式和位置配置
  5. 訪問控制:基於用户角色限制PDF訪問權限

總結

本文介紹瞭如何基於Mozilla PDF.js實現一個功能完善的PDF預覽組件,並重點講解了如何添加自定義的防下載和水印功能。通過合理的技術選型和組件設計,我們實現了一個既美觀又安全的PDF預覽解決方案。

在實際應用中,您可以根據具體需求進一步擴展功能,如添加頁面導航、文本搜索等高級特性,為用户提供更豐富的PDF閲讀體驗,同時確保文檔內容的安全性。

希望本文對您在Vue3項目中實現安全PDF預覽功能有所幫助!

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文檔,大家一起討論學習,一起進步。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.