pdfjs-dist
是 Mozilla 的 PDF.js 库的预构建版本,能让你在项目里展示 PDF 文件。下面为你介绍如何用 npm
安装 pdfjs-dist
并应用 pdf.js
和 pdf.worker.js
。
为了方便,我将使用 vite 搭建一个原生 js 项目。
1.创建项目
npm create vite@latest pdf-view
选:Vanilla
选:JavaScript
2.初始化项目
cd pdf-view
cnpm install
3.安装 pdfjs-dist
cnpm install pdfjs-dist@3.11.174 --save
编写 index1.html 如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF.js View 示例</title>
<link rel="stylesheet" href="/pdf.css" />
</head>
<body>
<div class="container">
<input type="file" id="fileInput" accept=".pdf">
<div class="controls">
<button id="prevPage">← 上一页</button>
<div class="page-control">
<input type="number" id="pageInput" min="1" step="1" disabled>
<button id="goButton" disabled>跳转</button>
<span>/ <span id="totalPages">-</span></span>
</div>
<button id="nextPage">下一页 →</button>
<div class="scale-control">
<button id="zoomOut">-</button>
<span id="scaleValue">100%</span>
<button id="zoomIn">+</button>
</div>
</div>
<div id="canvasContainer">
<canvas id="pdfCanvas"></canvas>
</div>
</div>
<script type="module" src="/pdf-view.js"></script>
</body>
</html>
编写 pdf.css 如下
.container {
max-width: 1000px;
margin: 10px auto;
padding: 15px;
}
.controls {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 15px;
}
#canvasContainer {
border: 1px solid #ddd;
overflow: auto;
max-height: 80vh;
position: relative;
}
canvas {
transition: transform 0.2s ease;
}
input[type="number"] {
width: 60px;
padding: 4px;
text-align: center;
}
button {
padding: 6px 12px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #6c757d;
cursor: not-allowed;
}
.scale-control {
margin-left: auto;
display: flex;
align-items: center;
gap: 5px;
}
编写 pdf-view.js 如下
import * as pdfjsLib from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.js';
// 初始化配置
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
// 状态管理对象
const state = {
currentPage: 1,
totalPages: 0,
scale: 1.0,
pdfDoc: null,
isRendering: false,
MIN_SCALE: 0.5,
MAX_SCALE: 3.0,
ZOOM_STEP: 0.1
};
// DOM 元素引用
const dom = {
canvas: document.getElementById('pdfCanvas'),
container: document.getElementById('canvasContainer'),
prevBtn: document.getElementById('prevPage'),
nextBtn: document.getElementById('nextPage'),
pageInput: document.getElementById('pageInput'),
goBtn: document.getElementById('goButton'),
totalPages: document.getElementById('totalPages'),
zoomIn: document.getElementById('zoomIn'),
zoomOut: document.getElementById('zoomOut'),
scaleValue: document.getElementById('scaleValue'),
fileInput: document.getElementById('fileInput')
};
// 初始化事件监听
function initEventListeners() {
// 文件选择
dom.fileInput.addEventListener('change', handleFileSelect);
// 翻页控制
dom.prevBtn.addEventListener('click', () => changePage(-1));
dom.nextBtn.addEventListener('click', () => changePage(1));
// 页面跳转
dom.goBtn.addEventListener('click', handlePageJump);
dom.pageInput.addEventListener('keypress', e => {
if (e.key === 'Enter') handlePageJump();
});
// 缩放控制
dom.zoomIn.addEventListener('click', () => changeScale(state.ZOOM_STEP));
dom.zoomOut.addEventListener('click', () => changeScale(-state.ZOOM_STEP));
// 鼠标滚轮缩放
dom.container.addEventListener('wheel', handleWheelZoom, { passive: false });
}
// 处理文件选择
async function handleFileSelect(e) {
const file = e.target.files[0];
if (!file) return;
try {
const arrayBuffer = await file.arrayBuffer();
state.pdfDoc = await pdfjsLib.getDocument(arrayBuffer).promise;
state.totalPages = state.pdfDoc.numPages;
initUI();
await renderPage();
} catch (err) {
console.error('文件加载失败:', err);
}
}
// 初始化界面状态
function initUI() {
dom.totalPages.textContent = state.totalPages;
dom.pageInput.max = state.totalPages;
dom.pageInput.value = 1;
dom.pageInput.disabled = false;
dom.goBtn.disabled = false;
dom.zoomIn.disabled = false;
dom.zoomOut.disabled = false;
}
// 核心渲染函数
async function renderPage() {
if (!state.pdfDoc || state.isRendering) return;
state.isRendering = true;
try {
const page = await state.pdfDoc.getPage(state.currentPage);
const viewport = page.getViewport({ scale: state.scale });
// 调整画布尺寸
dom.canvas.width = viewport.width;
dom.canvas.height = viewport.height;
// 渲染页面
await page.render({
canvasContext: dom.canvas.getContext('2d'),
viewport: viewport
}).promise;
updateUIState();
} catch (err) {
console.error('页面渲染失败:', err);
} finally {
state.isRendering = false;
}
}
// 更新界面状态
function updateUIState() {
dom.pageInput.value = state.currentPage;
dom.totalPages.textContent = state.totalPages;
dom.scaleValue.textContent = `${Math.round(state.scale * 100)}%`;
// 按钮状态
dom.prevBtn.disabled = state.currentPage <= 1;
dom.nextBtn.disabled = state.currentPage >= state.totalPages;
dom.zoomIn.disabled = state.scale >= state.MAX_SCALE;
dom.zoomOut.disabled = state.scale <= state.MIN_SCALE;
}
// 翻页处理
function changePage(offset) {
const newPage = state.currentPage + offset;
if (newPage < 1 || newPage > state.totalPages) return;
state.currentPage = newPage;
renderPage();
}
// 页面跳转处理
function handlePageJump() {
const target = parseInt(dom.pageInput.value) || 1;
const validPage = Math.max(1, Math.min(target, state.totalPages));
if (validPage !== state.currentPage) {
state.currentPage = validPage;
renderPage();
}
}
// 缩放处理
function changeScale(delta) {
state.scale = parseFloat(
Math.max(state.MIN_SCALE,
Math.min(state.scale + delta, state.MAX_SCALE))
.toFixed(1)
);
renderPage();
}
// 鼠标滚轮缩放处理
function handleWheelZoom(e) {
if (!e.ctrlKey) return;
e.preventDefault();
const delta = e.deltaY > 0 ? -state.ZOOM_STEP : state.ZOOM_STEP;
changeScale(delta);
}
// 启动应用
initEventListeners();
代码解释
- 导入
pdfjsLib
和pdfjsWorker
:借助 ES6 模块语法导入pdfjs-dist
库和pdf.worker.js
。 - 设置 worker 源:把
pdfjsLib.GlobalWorkerOptions.workerSrc
设为pdfjsWorker
,从而让 PDF.js 能够正确使用 Web Worker。 - 加载 PDF 文件:利用
pdfjsLib.getDocument
方法加载指定的 PDF 文件。 - 渲染第一页:获取 PDF 的第一页,创建一个 canvas 元素,然后把页面渲染到 canvas 上。
- 错误处理:使用
.catch
方法捕获并处理加载 PDF 文件时可能出现的错误。
运行 npm run dev
VITE v6.3.5 ready in 1066 ms
访问 http://localhost:5173/index1.html