基于PDF.js的安全PDF预览组件实现:从虚拟滚动到水印渲染
基于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");
}
});
}
两种模式对比:
| 特性 | Canvas渲染 | HTML渲染 |
|---|---|---|
| 性能 | 高 | 中等 |
| 文本选择 | 不支持 | 支持 |
| 缩放质量 | 高 | 中等 |
| 内存使用 | 高 | 低 |
| 兼容性 | 好 | 极好 |
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预览功能有所帮助!
需要源码的评论区回复6666