vue2利用canvas翻页浏览pdf文件
利用iframe纵向浏览文件
一、下载pdf.js包
链接:https://github.com/mozilla/pdf.js/tags
里面有很多的版本, 高版本的可能浏览器不兼容或者不兼容vue2,有时还要考虑移动端,最好下载低版本的,这里是v2.16.105-dist版本(选择带 dist
的压缩包,包含预编译文件)。)
二、引入vue2项目中
在本地static里面创建文件夹pdfjs,然后将下载包里面的文件放进pdfjs。
pdf.js包的目录结构,有的不一样,没关系,放进static里面就好
三、pdf浏览组件
<template><div class="pdf-viewer"><!-- 工具栏 --><div class="pdf-toolbar"><div class="page-controls"><button @click="prevPage" :disabled="currentPage <= 1"><i class="el-icon-arrow-left"></i></button><span><input type="number" v-model.number="pageInput" @change="goToPage" :min="1" :max="totalPages"class="page-input" /><!-- / 共 {{ totalPages }} 页 --></span><button @click="nextPage" :disabled="currentPage >= totalPages"><i class="el-icon-arrow-right"></i></button></div><div class="openBox"><button @click="zoomOut" :disabled="scale <= 0.5"><i class="el-icon-zoom-out"></i></button><span class="scale-info">{{ (scale * 100).toFixed(0) }}%</span><button @click="zoomIn" :disabled="scale >= 3"><i class="el-icon-zoom-in"></i></button></div></div><!-- PDF 内容区域 --><div class="pdf-content"><div class="loading" v-if="loading"><div class="spinner"></div><p>加载中...</p></div><div class="error" v-if="error">{{ error }}</div><div class="canvasstyle" v-else><canvas id="pdfCanvas"></canvas><!-- <iframe id="myIframe" :src="iframeSrc" width="100%" height="100%"></iframe> --></div></div></div>
</template><script>
export default {name: "PdfViewer",props: {// PDF 文件地址(本地路径或远程 URL)pdfUrl: {type: String,required: true,},},data() {return {pdfjsLib: null, // pdfjs 库实例pdfDoc: null, // PDF 文档实例currentPage: 1, // 当前页码totalPages: 0, // 总页数scale: 0.7, // 缩放比例pageInput: 1, // 页码输入框loading: true, // 加载状态error: "", // 错误信息iframeSrc: '../../../static/pdfjs-2.16.105-dist/web/viewer.html?file=' + '../../../static/pdf/1001.pdf#page=10', // 用于存储 iframe 的 src 属性};},watch: {currentPage(newVal) {this.pageInput = newVal;},scale() {this.renderPage(this.currentPage);},},mounted() {// 动态引入 pdf.min.jsthis.loadPdfJsLibrary();},methods: {// 加载 pdfjs 库loadPdfJsLibrary() {// 也可以直接下载html里面,是一个道理const script = document.createElement("script");// 路径对应 public/static/pdfjs/build/pdf.min.jsscript.src = "../../../static/pdfjs-2.16.105-dist/build/pdf.js";script.onload = () => {// 库加载完成后获取全局实例this.pdfjsLib = window.pdfjsLib;// 配置 worker 路径this.pdfjsLib.GlobalWorkerOptions.workerSrc ="../../../static/pdfjs-2.16.105-dist/build/pdf.worker.js";// 加载 PDF 文档this.loadPdfDocument();};script.onerror = () => {this.error = "PDF 库加载失败,请检查文件路径";this.loading = false;};document.head.appendChild(script);},// 加载 PDF 文档async loadPdfDocument() {try {this.loading = true;// 加载 PDF(支持本地路径或远程 URL)this.pdfDoc = await this.pdfjsLib.getDocument(this.pdfUrl).promise;this.totalPages = this.pdfDoc.numPages;this.renderPage(1); // 渲染第一页} catch (err) {this.error = `PDF 加载失败: ${err.message}`;console.error("加载错误:", err);} finally {this.loading = false;}},// 渲染指定页码async renderPage(pageNum) {if (!this.pdfDoc) return;try {this.loading = true;const page = await this.pdfDoc.getPage(pageNum);// 根据缩放比例计算视图大小const viewport = page.getViewport({ scale: this.scale });// 设置 canvas 尺寸并渲染const canvas = document.getElementById("pdfCanvas");const ctx = canvas.getContext("2d");canvas.width = viewport.width;canvas.height = viewport.height;await page.render({canvasContext: ctx,viewport: viewport,}).promise;this.currentPage = pageNum;} catch (err) {this.error = `页面渲染失败: ${err.message}`;console.error("渲染错误:", err);} finally {this.loading = false;}},// 上一页prevPage() {if (this.currentPage > 1) {this.renderPage(this.currentPage - 1);}},// 下一页nextPage() {if (this.currentPage < this.totalPages) {this.renderPage(this.currentPage + 1);}},// 跳转到指定页goToPage() {let page = Math.floor(this.pageInput);// 边界校验if (page < 1) page = 1;if (page > this.totalPages) page = this.totalPages;if (page !== this.currentPage) {this.renderPage(page);}},// 放大zoomIn() {this.scale += 0.1;},// 缩小zoomOut() {this.scale -= 0.1;},},
};
</script><style scoped>
.pdf-viewer {max-width: 100%;margin: 0 auto;
}.pdf-toolbar {display: flex;align-items: center;justify-content: space-between;padding: 10px 2px;
}.openBox {display: flex;align-items: center;
}.pdf-toolbar button {padding: 4px 6px;border: none;background-color: #ffffff;border: 1px solid #e5e5e5;/* color: white; */border-radius: 4px;/* cursor: pointer; *//* transition: background-color 0.2s; */
}.pdf-toolbar button:disabled {background-color: #cccccc93;cursor: not-allowed;
}
.scale-info {margin: 0 10px;min-width: 50px;text-align: center;
}.page-controls {display: flex;align-items: center;
}.page-input {width: 50px;padding: 5px;margin: 0 4px;text-align: center;border: 1px solid #e5e5e5;
}.jump {margin-left: 4px;
}.pdf-content {width: 100%;text-align: center;border: 1px solid #e0e0e0;border-radius: 8px;background-color: #ffffff;display: flex;flex-direction: column;justify-content: center;min-height: 300px;
}.canvasstyle {width: 100%;overflow: auto;text-align: center;
}.canvasstyle::-webkit-scrollbar {display: none;/* Chrome Safari */
}.loading {width: 100%;text-align: center;padding: 50px 0;
}.spinner {width: 40px;height: 40px;margin: 0 auto;border: 4px solid #f3f3f3;border-top: 4px solid #4285f4;border-radius: 50%;animation: spin 1s linear infinite;
}@keyframes spin {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}
}.error {color: #dc3545;text-align: center;padding: 50px 0;
}
</style>
四、父组件使用
直接引入传值就行
<PdfViewer :pdfUrl="pdfUrl" />