Vue2.0和pdfjs相互结合使用
本篇章记录:V2.0和PDF.Js相互结合使用案例摘要!!
安装:
第一步:npm install pdfjs-dist@2.2.228
第二步:npm i path2d-polyfill "path2d-polyfill": "^3.1.2",
根据报错依次安装(可选,不一定)
npm install --save canvas
npm i -g node-gyp
npm install canvas --canvas_binary_host_mirror=https://registry.npmmirror.com/-/binary/canvas
引用后页面会报错:
Uncaught SyntaxError: Unexpected token '<'
vue.js:1872 Error loading PDF: Error: Setting up fake worker failed: "Cannot read properties of undefined (reading 'WorkerMessageHandler')".
需要在index.html下引用:
例如:
<script src="<%= BASE_URL %>cdn/pdfjs-dist/build/pdf.worker.min.js"></script>
下载pdfjs-dist依赖时,出现版本不兼容问题:
在vue.config.js的module.exports={
transpileDependencies: ['@jiaminghi', '@microsoft/signalr', 'pdfjs-dist'], //此处增加pdfjs-dist 用于低版本兼容,自动解析语法错误
}
import * as pdfjsLib from 'pdfjs-dist';
<div id="pdfViewer" v-show="Ispdf">
</div>
var pdfViewer = document.getElementById('pdfViewer');
pdfjsLib.GlobalWorkerOptions.workerSrc = '../bgcode/pdfjs/pdf.worker.min.js';
pdfjsLib.getDocument(url).promise.then(pdfDoc => {
let renderingTasks = [];
for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) {
renderingTasks.push(pdfDoc.getPage(pageNum).then(function(page) {
console.log(pageNum);
console.log(page);
// console.log(page);
var canvas = document.createElement('canvas');
canvas.classList.add('i_pagep')
canvas.id = pageNum;
var context = canvas.getContext('2d');
var viewport = page.getViewport({
scale: 1.5
});
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: context,
viewport: viewport
};
//console.log(renderContext);
page.render(renderContext).promise
.then(function() {
pdfViewer.appendChild(canvas);
});
}));
}
// 8. 等待所有渲染任务完成
Promise.all(renderingTasks).then(() => {
console.log('所有页面都已按顺序渲染为图片。');
});
// // 获取第一页
// pdf.getPage(1).then(page => {
// console.log(page);
// // const scale = 1.5; // 缩放比例
// // const viewport = page.getViewport({ scale });
// // // 准备 canvas 使用 PDF 渲染内容
// // const canvas = this.$refs.pdfCanvas;
// // const context = canvas.getContext('2d');
// // canvas.height = viewport.height;
// // canvas.width = viewport.width;
// // // 渲染 PDF 页
// // const renderContext = {
// // canvasContext: context,
// // viewport: viewport,
// // };
// // page.render(renderContext);
// });
}).catch(error => {
console.error('Error loading PDF:', error);
});
html中vue和pdf.js的应用
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF.js 预览 Demo</title>
<!-- Element Plus -->
<link rel="stylesheet" href="https://unpkg.com/element-plus@2.4.4/dist/index.css">
<!-- Vue -->
<script src="https://unpkg.com/vue@3.4.21/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/element-plus@2.4.4/dist/index.full.js"></script>
<!-- PDF.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a2e;
color: #fff;
min-height: 100vh;
}
.demo-header {
text-align: center;
padding: 28px 20px 16px;
background: linear-gradient(135deg, #16213e, #1a1a2e);
border-bottom: 1px solid rgba(255,255,255,0.06);
}
.demo-header h1 {
font-size: 22px;
font-weight: 600;
color: #fff;
margin-bottom: 6px;
}
.demo-header p {
font-size: 13px;
color: rgba(255,255,255,0.45);
}
/* 文件选择区 */
.file-picker {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 16px 20px;
background: #20213a;
border-bottom: 1px solid rgba(255,255,255,0.06);
}
.file-picker .el-input {
max-width: 480px;
flex: 1;
}
.file-picker .el-input__wrapper {
background: rgba(255,255,255,0.07) !important;
border: 1px solid rgba(255,255,255,0.12) !important;
box-shadow: none !important;
}
.file-picker .el-input__inner {
color: rgba(255,255,255,0.8) !important;
}
.file-picker .el-input__inner::placeholder {
color: rgba(255,255,255,0.3) !important;
}
.file-picker .el-button {
flex-shrink: 0;
}
/* PDF.js 预览容器 */
.pdf-viewer-wrap {
width: 100%;
height: calc(100vh - 160px);
display: flex;
flex-direction: column;
overflow: hidden;
background: #525659;
}
.pdf-toolbar {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 8px 12px;
background: #3d4043;
color: #fff;
font-size: 13px;
flex-shrink: 0;
user-select: none;
}
.pdf-toolbar button {
background: rgba(255,255,255,0.15);
border: none;
color: #fff;
border-radius: 4px;
padding: 4px 10px;
font-size: 13px;
cursor: pointer;
line-height: 1.5;
touch-action: manipulation;
min-width: 32px;
transition: background 0.15s;
}
.pdf-toolbar button:hover {
background: rgba(255,255,255,0.25);
}
.pdf-toolbar button:active {
background: rgba(255,255,255,0.35);
}
.pdf-toolbar button:disabled {
opacity: 0.35;
cursor: default;
}
.pdf-page-info {
min-width: 80px;
text-align: center;
font-size: 13px;
color: #ccc;
}
.pdf-scale-btns {
display: flex;
gap: 4px;
}
.pdf-scale-btns button {
min-width: 34px;
}
.pdf-scale-btns button.zoom-label {
min-width: 52px;
font-size: 12px;
cursor: pointer;
}
.pdf-canvas-scroll {
flex: 1;
overflow-y: auto;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 0;
gap: 8px;
background: #525659;
scroll-behavior: smooth;
}
.pdf-canvas-scroll canvas {
display: block;
box-shadow: 0 2px 16px rgba(0,0,0,0.5);
background: #fff;
max-width: 100%;
}
.pdf-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #ccc;
font-size: 14px;
gap: 14px;
}
.pdf-spinner {
width: 38px;
height: 38px;
border: 3px solid rgba(255,255,255,0.15);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 空状态 */
.pdf-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: calc(100vh - 160px);
gap: 16px;
color: rgba(255,255,255,0.35);
background: #1a1a2e;
}
.pdf-empty .empty-icon {
font-size: 56px;
opacity: 0.3;
}
.pdf-empty p {
font-size: 14px;
text-align: center;
line-height: 1.6;
}
/* 提示文字 */
.pdf-hint {
text-align: center;
padding: 8px 20px;
font-size: 12px;
color: rgba(255,255,255,0.3);
background: #1a1a2e;
border-top: 1px solid rgba(255,255,255,0.04);
}
</style>
</head>
<body>
<div id="app">
<!-- 标题区 -->
<div class="demo-header">
<h1>📕 PDF.js 预览 Demo</h1>
<p>支持翻页 · 缩放按钮 · 鼠标滚轮缩放</p>
</div>
<!-- 文件路径选择区 -->
<div class="file-picker">
<el-input
v-model="pdfFilePath"
placeholder="输入 PDF 文件路径,例如:C:\Users\xxx\Downloads\测试.pdf"
clearable
@keyup.enter="loadPdfFromPath"
></el-input>
<el-button type="primary" @click="loadPdfFromPath">加载 PDF</el-button>
<el-button @click="pickFile">选择文件</el-button>
<!-- 隐藏的文件选择器 -->
<input
ref="fileInputRef"
type="file"
accept="application/pdf"
style="display:none"
@change="handleFileChange"
/>
</div>
<!-- PDF 预览区 -->
<div class="pdf-viewer-wrap" v-if="hasLoaded">
<!-- 工具栏 -->
<div class="pdf-toolbar">
<button :disabled="pdfCurrentPage <= 1" @click="pdfPrevPage" title="上一页">←</button>
<span class="pdf-page-info">{{ pdfCurrentPage }} / {{ pdfTotalPages }}</span>
<button :disabled="pdfCurrentPage >= pdfTotalPages" @click="pdfNextPage" title="下一页">→</button>
<div class="pdf-scale-btns">
<button @click="pdfZoomOut" title="缩小">-</button>
<button class="zoom-label" @click="pdfZoomReset" title="重置为 300%">{{ Math.round(pdfScale * 100) }}%</button>
<button @click="pdfZoomIn" title="放大">+</button>
</div>
</div>
<!-- Canvas 渲染区 -->
<div class="pdf-canvas-scroll" ref="pdfScrollRef" @wheel.prevent="handlePdfWheel">
<div v-if="pdfLoading" class="pdf-loading">
<div class="pdf-spinner"></div>
<span>PDF 加载中...</span>
</div>
<canvas v-show="!pdfLoading" ref="pdfCanvasRef"></canvas>
</div>
</div>
<!-- 空状态 -->
<div class="pdf-empty" v-else>
<div class="empty-icon">📄</div>
<p>请在上方输入 PDF 文件路径<br>然后点击"加载 PDF"按钮</p>
</div>
<!-- 底部提示 -->
<div class="pdf-hint" v-if="hasLoaded">
💡 提示:在 PDF 区域内滚动鼠标滚轮可放大/缩小 · 点击百分比数字可重置为 300%
</div>
</div>
<script>
const { createApp, ref, nextTick } = Vue;
const { ElMessage } = ElementPlus;
const app = createApp({
setup() {
// ---------- 文件选择 ----------
const fileInputRef = ref(null);
const pdfFilePath = ref('');
// ---------- PDF.js 相关 ----------
const pdfCanvasRef = ref(null);
const pdfScrollRef = ref(null);
const pdfLoading = ref(false);
const pdfCurrentPage = ref(1);
const pdfTotalPages = ref(0);
const pdfScale = ref(3.0); // 默认 300%
let pdfDocInstance = null;
let pdfRenderTask = null;
// 基准缩放:首次加载时根据容器宽度计算一次,后续基于它缩放
let pdfBaseScale = 0;
// 是否已加载过(用于显示/隐藏预览区)
const hasLoaded = ref(false);
// 配置 pdf.js worker
if (typeof pdfjsLib !== 'undefined') {
pdfjsLib.GlobalWorkerOptions.workerSrc =
'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
}
// ---------- 渲染指定页 ----------
const renderPdfPage = async (pageNum) => {
if (!pdfDocInstance) return;
if (pdfRenderTask) {
pdfRenderTask.cancel();
pdfRenderTask = null;
}
try {
const page = await pdfDocInstance.getPage(pageNum);
const canvas = pdfCanvasRef.value;
if (!canvas) return;
const ctx = canvas.getContext('2d');
// 首次加载:基于容器宽度计算基准缩放(只算一次)
if (pdfBaseScale === 0) {
const containerWidth = (pdfScrollRef.value ? pdfScrollRef.value.clientWidth : 500) - 24;
const viewport = page.getViewport({ scale: 1 });
pdfBaseScale = containerWidth / viewport.width;
}
const finalScale = pdfBaseScale * pdfScale.value;
const scaledViewport = page.getViewport({ scale: finalScale });
canvas.width = scaledViewport.width;
canvas.height = scaledViewport.height;
const renderContext = {
canvasContext: ctx,
viewport: scaledViewport
};
pdfRenderTask = page.render(renderContext);
await pdfRenderTask.promise;
pdfRenderTask = null;
if (pdfScrollRef.value) {
pdfScrollRef.value.scrollTop = 0;
}
} catch (e) {
if (e && e.name !== 'RenderingCancelledException') {
console.error('PDF 渲染出错:', e);
}
}
};
// ---------- 加载 PDF ----------
const loadPdf = async (pathOrUrl) => {
if (typeof pdfjsLib === 'undefined') {
ElMessage.error('PDF.js 未加载');
return;
}
hasLoaded.value = false;
pdfBaseScale = 0;
pdfLoading.value = true;
pdfCurrentPage.value = 1;
pdfTotalPages.value = 0;
pdfDocInstance = null;
try {
// 优先尝试作为 URL 直接加载(网络路径或 file:// 路径)
const loadingTask = pdfjsLib.getDocument({ url: pathOrUrl, withCredentials: false });
const pdfDoc = await loadingTask.promise;
pdfDocInstance = pdfDoc;
pdfTotalPages.value = pdfDoc.numPages;
pdfLoading.value = false;
hasLoaded.value = true;
await nextTick();
await renderPdfPage(1);
ElMessage.success('PDF 加载成功,共 ' + pdfDoc.numPages + ' 页');
} catch (e) {
pdfLoading.value = false;
hasLoaded.value = false;
console.error('PDF 加载失败:', e);
ElMessage.error('PDF 加载失败,请检查文件路径是否正确,或换用"选择文件"按钮');
}
};
// ---------- 翻页 ----------
const pdfPrevPage = async () => {
if (pdfCurrentPage.value > 1) {
pdfCurrentPage.value--;
await renderPdfPage(pdfCurrentPage.value);
}
};
const pdfNextPage = async () => {
if (pdfCurrentPage.value < pdfTotalPages.value) {
pdfCurrentPage.value++;
await renderPdfPage(pdfCurrentPage.value);
}
};
// ---------- 缩放(按钮) ----------
// 放大(最大 500%)
const pdfZoomIn = async () => {
pdfScale.value = Math.min(5.0, parseFloat((pdfScale.value + 0.25).toFixed(2)));
await renderPdfPage(pdfCurrentPage.value);
};
// 缩小(最小 10%)
const pdfZoomOut = async () => {
pdfScale.value = Math.max(0.1, parseFloat((pdfScale.value - 0.25).toFixed(2)));
await renderPdfPage(pdfCurrentPage.value);
};
// 重置为 300%
const pdfZoomReset = async () => {
pdfScale.value = 3.0;
await renderPdfPage(pdfCurrentPage.value);
};
// ---------- 滚轮缩放 ----------
const handlePdfWheel = async (e) => {
if (!pdfDocInstance) return;
e.preventDefault();
const delta = e.deltaY || e.wheelDelta;
if (delta > 0) {
pdfScale.value = Math.max(0.1, parseFloat((pdfScale.value - 0.15).toFixed(2)));
} else {
pdfScale.value = Math.min(5.0, parseFloat((pdfScale.value + 0.15).toFixed(2)));
}
await renderPdfPage(pdfCurrentPage.value);
};
// ---------- 文件选择 ----------
// 点击"选择文件"按钮
const pickFile = () => {
fileInputRef.value && fileInputRef.value.click();
};
// 文件选择后处理
const handleFileChange = async (e) => {
const file = e.target.files && e.target.files[0];
if (!file) return;
pdfFilePath.value = file.name;
if (file.path) {
// Electron 环境下 file.path 包含完整路径
await loadPdf(file.path);
} else {
// 普通浏览器环境:使用 File API 创建 URL
const url = URL.createObjectURL(file);
// 重置 worker 方式
pdfDocInstance = null;
pdfBaseScale = 0;
pdfLoading.value = true;
pdfCurrentPage.value = 1;
pdfTotalPages.value = 0;
hasLoaded.value = false;
try {
const loadingTask = pdfjsLib.getDocument({ url: url, withCredentials: false });
const pdfDoc = await loadingTask.promise;
pdfDocInstance = pdfDoc;
pdfTotalPages.value = pdfDoc.numPages;
pdfLoading.value = false;
hasLoaded.value = true;
await nextTick();
await renderPdfPage(1);
ElMessage.success('PDF 加载成功,共 ' + pdfDoc.numPages + ' 页');
} catch (err) {
pdfLoading.value = false;
hasLoaded.value = false;
ElMessage.error('PDF 加载失败');
}
}
// 清空 input,允许重复选择同一文件
e.target.value = '';
};
// 从路径输入框加载
const loadPdfFromPath = async () => {
const path = pdfFilePath.value.trim();
if (!path) {
ElMessage.warning('请输入 PDF 文件路径');
return;
}
await loadPdf(path);
};
return {
// 文件
fileInputRef,
pdfFilePath,
pickFile,
handleFileChange,
loadPdfFromPath,
// PDF 状态
hasLoaded,
pdfCanvasRef,
pdfScrollRef,
pdfLoading,
pdfCurrentPage,
pdfTotalPages,
pdfScale,
// 方法
pdfPrevPage,
pdfNextPage,
pdfZoomIn,
pdfZoomOut,
pdfZoomReset,
handlePdfWheel,
};
}
});
app.use(ElementPlus);
app.mount('#app');
</script>
</body>
</html>

浙公网安备 33010602011771号