🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
基於PDF.js的安全PDF預覽組件實現:從虛擬滾動到水印渲染
本文將詳細介紹如何基於Mozilla PDF.js實現一個功能完善、安全可靠的PDF預覽組件,重點講解虛擬滾動、雙模式渲染、水印實現等核心技術。
前言
在Web應用中實現PDF預覽功能是常見需求,尤其是在線教育、文檔管理等場景。然而,簡單的PDF預覽往往無法滿足實際業務需求,特別是在安全性方面。本文將介紹如何基於PDF.js實現一個功能完善的PDF預覽組件,並重點講解如何添加自定義防下載和水印功能,為文檔安全提供保障。
功能概覽
我們的PDF預覽組件實現了以下核心功能:
- 基礎功能:PDF文件加載與渲染、自定義尺寸控制、頁面縮放規則配置、主題切換
- 安全增強:動態水印添加、防下載功能、右鍵菜單禁用、打印控制
- 用户體驗:頁面渲染事件通知、響應式佈局適配、加載狀態反饋
技術實現
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支持兩種渲染模式,可根據不同需求選擇。兩種渲染方式在視覺效果和性能上有明顯差異:
圖:HTML渲染模式下的PDF顯示效果
圖: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");
}
});
}
兩種模式對比:
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();
// 只渲染可見頁面,延遲渲染其他頁面
};
注意事項
- PDF.js版本:確保使用兼容的PDF.js版本,不同版本API可能有差異
- 跨域處理:PDF文件可能存在跨域問題,需確保服務器配置了正確的CORS頭
- 大文件處理:對於大型PDF文件,考慮添加加載進度提示
- 移動端適配:在移動設備上可能需要額外的樣式調整
- 安全限制:雖然實現了防下載和水印,但無法完全防止技術用户獲取PDF內容
擴展功能建議
- 頁面跳轉:添加頁面導航功能,支持直接跳轉到指定頁面
- 文本搜索:實現PDF內容搜索功能
- 註釋工具:添加PDF註釋、標記功能
- 水印樣式自定義:支持更多水印樣式和位置配置
- 訪問控制:基於用户角色限制PDF訪問權限
總結
本文介紹瞭如何基於Mozilla PDF.js實現一個功能完善的PDF預覽組件,並重點講解了如何添加自定義的防下載和水印功能。通過合理的技術選型和組件設計,我們實現了一個既美觀又安全的PDF預覽解決方案。
在實際應用中,您可以根據具體需求進一步擴展功能,如添加頁面導航、文本搜索等高級特性,為用户提供更豐富的PDF閲讀體驗,同時確保文檔內容的安全性。
希望本文對您在Vue3項目中實現安全PDF預覽功能有所幫助!