使用pdfjs-dist预览pdf,实现预加载,滚动条翻页。pdfjs的版本很重要,换了好多版本,终于有一个能用的
javascript">node 20.18.1
"pdfjs-dist": "^2.2.228",
vue页面代码如下
javascript"><template><div v-loading="loading"><div class="fixed-toolbar"><div class="flex flex-direction"><div class="mb4"><el-button @click="onClose()" size="mini"type="warning">返 回</el-button></div><div class="mb4"><el-button @click="switchViewMode()" size="mini" type="success">切换模式</el-button></div><div class="mb4"><el-button @click="scalBig()" size="mini"type="primary">放 大</el-button></div><!-- <div><el-button class="mb4" @click="renderPdf(1)" size="mini" type="primary">默认</el-button></div> --><div><el-button @click="scalSmall()" size="mini"type="primary">缩 小</el-button></div><el-dropdown size="mini" trigger="click" class="more-dropdown" style="line-height: 35px;"><el-button size="mini" type="primary">缩 放</el-button><el-dropdown-menu slot="dropdown"><el-dropdown-item><span class="el-button--text-" @click="scale = 1">1X</span></el-dropdown-item><el-dropdown-item><span class="el-button--text-" @click="scale = 2">2X</span></el-dropdown-item><el-dropdown-item><span class="el-button--text-" @click="scale = 3">3X</span></el-dropdown-item></el-dropdown-menu></el-dropdown></div></div><!-- PDF容器 --><div ref="pdfContainer" class="pdf-container" @scroll="handleScroll"><!-- 占位符,用于撑开滚动区域 --><div :style="{ height: totalHeight + 'px' }"></div><!-- 渲染可见页面 --><div v-for="page in visiblePages" :key="page.pageNumber" :ref="`page-${page.pageNumber}`" class="page-container":style="{ top: getPageTop(page.pageNumber) + 'px' }"><canvas :ref="`canvas-${page.pageNumber}`"></canvas><div v-if="page.loading" class="loading-indicator">加载中...</div></div></div></div>
</template><script>
import pdfjsLib from 'pdfjs-dist/build/pdf';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
import { debounce } from 'lodash';pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;export default {data() {return {pdfDoc: null,loading: true,totalPages: 0,pageHeights: [],totalHeight: 0,visiblePages: [],scale: 1,baseScale: 1, // 移动端基准缩放比例scrollTop: 0,viewport: null,renderTasks: {},loadedPages: {}, // 新增:记录已加载的页面desiredTotal: 5, // 每次加载的页面数量canvasPool: []};},mounted() {const pageSize = this.$route.query.pageSizethis.desiredTotal = pageSize?pageSize:this.desiredTotalconsole.log('预加载页面数量: -->', this.desiredTotal);this.loadPdf(this.pdfUrl);this.debouncedHandleScroll = debounce(this.updateVisiblePages, 100);this.debouncedHandleResize = debounce(() => {this.calculatePageHeights();this.updateVisiblePages();}, 100);window.addEventListener('resize', this.handleResize);},beforeDestroy() {window.removeEventListener('resize', this.handleResize);this.debouncedHandleScroll.cancel();this.debouncedHandleResize.cancel();Object.values(this.renderTasks).forEach((task) => task.cancel());},computed: {pdfUrl() {return this.$route.query.pdfUrl;},},watch: {scale(newVal) {this.loadedPages = {}this.updateVisiblePages()}},methods: {switchViewMode() {this.$router.replace({name: 'pagePreviewPdf',query: {pdfUrl: this.pdfUrl}})},onClose() {this.$router.go(-1)},// 放大scalBig() {this.scale = this.scale + 0.2},// 缩小scalSmall() {if (this.scale > 1.2) {this.scale = this.scale - 0.2}},async loadPdf(url) {try {const loadingTask = pdfjsLib.getDocument(url);this.pdfDoc = await loadingTask.promise;this.totalPages = this.pdfDoc.numPages;await this.calculatePageHeights();this.updateVisiblePages();this.loading = false;} catch (error) {console.error('加载PDF失败:', error);this.loading = false;this.$message.error('加载PDF失败:' + error);}},handleScroll() {this.debouncedHandleScroll();},handleResize() {this.debouncedHandleResize();},async calculatePageHeights() {this.pageHeights = [];let page = await this.pdfDoc.getPage(1);let viewport = page.getViewport({ scale: 1 });this.baseScale = this.isMobile ? window.innerWidth / viewport.width : 1for (let i = 1; i <= this.totalPages; i++) {const page = await this.pdfDoc.getPage(i);const viewport = page.getViewport({ scale: this.scale*this.baseScale });this.pageHeights.push(viewport.height);console.log(i, ' 页面高度:', viewport.height, 'px,页面宽度:', viewport.width, 'px')}this.totalHeight = this.pageHeights.reduce((sum, height) => sum + height, 0);},getPageTop(pageNumber) {const top = this.pageHeights.slice(0, pageNumber - 1).reduce((sum, height) => sum + height, 0)// console.log('当前显示页面top:', top)return top;},updateVisiblePages() {const container = this.$refs.pdfContainer;const scrollTop = container.scrollTop;const containerHeight = container.clientHeight;// 计算初始可见范围let startPage = 1;let endPage = this.totalPages;let currentHeight = 0;// 计算起始页for (let i = 0; i < this.pageHeights.length; i++) {currentHeight += this.pageHeights[i];if (currentHeight > scrollTop) {startPage = i + 1;break;}}// 计算结束页currentHeight = 0;for (let i = 0; i < this.pageHeights.length; i++) {currentHeight += this.pageHeights[i];if (currentHeight > scrollTop + containerHeight) {endPage = i + 1;break;}}// 扩展预加载范围,总页数不超过10const desiredTotal = this.desiredTotal;const visibleCount = endPage - startPage + 1;let remaining = desiredTotal - visibleCount;if (remaining > 0) {let preloadBefore = Math.floor(remaining / 2);let preloadAfter = remaining - preloadBefore;let newStart = Math.max(1, startPage - preloadBefore);let newEnd = Math.min(this.totalPages, endPage + preloadAfter);// 边界调整const addedBefore = startPage - newStart;const addedAfter = newEnd - endPage;if (addedBefore < preloadBefore) {newEnd = Math.min(this.totalPages, newEnd + (preloadBefore - addedBefore));} else if (addedAfter < preloadAfter) {newStart = Math.max(1, newStart - (preloadAfter - addedAfter));}startPage = newStart;endPage = newEnd;// 确保不超过总页数限制if (endPage - startPage + 1 > desiredTotal) {endPage = startPage + desiredTotal - 1;if (endPage > this.totalPages) endPage = this.totalPages;}} else {// 可见页数超过10时调整endPage = startPage + desiredTotal - 1;if (endPage > this.totalPages) {endPage = this.totalPages;startPage = Math.max(1, endPage - desiredTotal + 1);}}// 生成可见页面数组this.visiblePages = [];console.log('渲染显示范围:', startPage, ' --- ', endPage)const pages = []for (let j = startPage; j <= endPage; j++) {pages.push(j + '')}console.log('>>>>loadedPages:', JSON.stringify(this.loadedPages))// 移除不在显示区的页面Object.keys(this.loadedPages).filter(k => !pages.includes(k)).forEach(pageNumber => {this.$delete(this.loadedPages, pageNumber);})console.log('>>>>cleaned loadedPages:', JSON.stringify(this.loadedPages))for (let i = startPage; i <= endPage; i++) {const isLoaded = !!this.loadedPages[i];this.visiblePages.push({pageNumber: i,loading: !isLoaded,});if (!isLoaded) {this.renderPage(i);console.log('渲染', i)} else {console.log('DONE:', i)}}},async renderPage(pageNumber) {console.log('>>>>loadedPages:', JSON.stringify(this.loadedPages))if (this.loadedPages[pageNumber]) {const index = this.visiblePages.findIndex((p) => p.pageNumber === pageNumber);if (index !== -1) this.$set(this.visiblePages[index], 'loading', false);return;}if (this.renderTasks[pageNumber]) this.renderTasks[pageNumber].cancel();try {const page = await this.pdfDoc.getPage(pageNumber);const canvas = this.$refs[`canvas-${pageNumber}`][0];const context = canvas.getContext('2d');const viewport = page.getViewport({ scale: this.scale*this.baseScale });canvas.height = viewport.height;canvas.width = viewport.width;const renderTask = page.render({ canvasContext: context, viewport });this.renderTasks[pageNumber] = renderTask;await renderTask.promise;this.$set(this.loadedPages, pageNumber, true); // 标记为已加载const index = this.visiblePages.findIndex((p) => p.pageNumber === pageNumber);if (index !== -1) this.$set(this.visiblePages[index], 'loading', false);} catch (error) {if (error.name !== 'RenderingCancelledException') console.error('渲染失败:', error);}},},
};
</script><style lang="scss" scoped>
.pdf-container {height: 100vh;overflow-y: auto;width: 100%;position: relative;
}.page-container {position: absolute;width: 100%;background: #f5f5f5;margin-bottom: 20px;display: flex;justify-content: center;
}.loading-indicator {text-align: center;padding: 20px;
}.fixed-toolbar {position: fixed;bottom: 50%;right: 0;background-color: white;/* 可选:设置背景颜色 */opacity: 0.7;z-index: 1000;/* 确保工具栏在其他内容之上 */padding: 10px;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);/* 可选:添加阴影效果 */margin-bottom: 10px;flex-wrap: wrap;
}.mb4 {margin-bottom: 4px;
}@media screen and (max-width: 768px) {}
</style>