实现PDF预览的H5方案
在Web应用中预览PDF文件是常见需求,HTML5提供了多种实现方式。以下是几种主流技术方案及代码示例。
使用iframe嵌入PDF
最简单的方式是通过iframe直接嵌入PDF文件,浏览器会自动调用内置PDF查看器。
<iframe
src="/path/to/file.pdf"
width="100%"
height="600px"
style="border: none;">
</iframe>
优点:零依赖、实现简单。缺点:样式无法自定义,移动端兼容性较差。
PDF.js方案
Mozilla开源的PDF.js库提供更灵活的PDF渲染方案,支持自定义UI和交互。
安装依赖:
npm install pdfjs-dist
基础实现代码:
// 初始化PDF.js
const pdfjsLib = require('pdfjs-dist');
async function renderPDF(url, container) {
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
// 渲染第一页
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.0 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
container.appendChild(canvas);
await page.render({
canvasContext: context,
viewport: viewport
}).promise;
}
// 调用示例
renderPDF('document.pdf', document.getElementById('pdf-container'));
自定义PDF查看器
基于PDF.js构建完整查看器:
class PDFViewer {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.currentPage = 1;
this.pdfDoc = null;
}
async loadPDF(url) {
const loadingTask = pdfjsLib.getDocument(url);
this.pdfDoc = await loadingTask.promise;
this.renderPage(this.currentPage);
}
async renderPage(num) {
const page = await this.pdfDoc.getPage(num);
const viewport = page.getViewport({ scale: 1.5 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
this.container.innerHTML = '';
this.container.appendChild(canvas);
await page.render({
canvasContext: context,
viewport: viewport
}).promise;
}
nextPage() {
if(this.currentPage < this.pdfDoc.numPages) {
this.currentPage++;
this.renderPage(this.currentPage);
}
}
prevPage() {
if(this.currentPage > 1) {
this.currentPage--;
this.renderPage(this.currentPage);
}
}
}
移动端优化方案
针对移动设备需考虑触控交互和性能优化:
// 添加触控事件支持
const viewer = new PDFViewer('pdf-container');
viewer.loadPDF('document.pdf');
let startX = 0;
let endX = 0;
document.getElementById('pdf-container').addEventListener('touchstart', (e) => {
startX = e.changedTouches[0].screenX;
});
document.getElementById('pdf-container').addEventListener('touchend', (e) => {
endX = e.changedTouches[0].screenX;
if(startX - endX > 50) {
viewer.nextPage();
} else if(endX - startX > 50) {
viewer.prevPage();
}
});
// 实现页面缓存
const pageCache = new Map();
async function getPage(num) {
if(pageCache.has(num)) {
return pageCache.get(num);
}
const page = await pdfDoc.getPage(num);
pageCache.set(num, page);
return page;
}
性能优化技巧
大型PDF文件需要特殊处理:
// 分块加载
async function progressiveRender(pageNum) {
const page = await pdfDoc.getPage(pageNum);
const viewport = page.getViewport({ scale: 1.0 });
const canvas = document.createElement('canvas');
canvas.height = viewport.height;
canvas.width = viewport.width;
const ctx = canvas.getContext('2d');
// 先渲染低质量图像
const renderContext = {
canvasContext: ctx,
viewport: viewport,
intent: 'preview'
};
await page.render(renderContext).promise;
// 再渲染高质量图像
renderContext.intent = 'print';
await page.render(renderContext).promise;
}
// Web Worker处理
const worker = new Worker('pdf.worker.js');
pdfjsLib.GlobalWorkerOptions.workerPort = worker;
实现PDF文本搜索
PDF.js支持文本层渲染和搜索功能:
async function initTextLayer(pageNum) {
const page = await pdfDoc.getPage(pageNum);
const textContent = await page.getTextContent();
const textLayer = new pdfjsLib.TextLayerBuilder({
textLayerDiv: document.getElementById('text-layer'),
pageIndex: page.pageIndex,
viewport: page.getViewport({ scale: 1.0 })
});
textLayer.setTextContent(textContent);
textLayer.render();
}
async function searchText(query) {
const matches = [];
for(let i = 1; i <= pdfDoc.numPages; i++) {
const page = await pdfDoc.getPage(i);
const textContent = await page.getTextContent();
textContent.items.forEach(item => {
if(item.str.includes(query)) {
matches.push({
page: i,
text: item.str
});
}
});
}
return matches;
}
完整示例代码
整合上述功能的完整实现:
<!DOCTYPE html>
<html>
<head>
<title>PDF Viewer</title>
<style>
#viewer-container {
position: relative;
overflow: auto;
}
#pdf-canvas {
border: 1px solid #ccc;
}
#text-layer {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
.search-box {
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="search-box">
<input type="text" id="search-input">
<button id="search-btn">Search</button>
</div>
<div id="viewer-container">
<canvas id="pdf-canvas"></canvas>
<div id="text-layer"></div>
</div>
<script src="pdf.js"></script>
<script>
// 完整实现代码...
</script>
</body>
</html>
以上方案可根据实际需求组合使用,PDF.js方案最灵活但实现复杂度较高,iframe方案最简便但定制性差。在移动端应用中,建议结合Web Worker和分页加载技术优化性能。