页面的最终效果,
根据静态的pdf自动生成左侧pdf目录,右侧使用pdfjs预览,点击左侧目录,右侧滚动到pdf对应的页面,
(注意:pdf必须包含目录页面,否则无法生成目录)
然后我们放一下页面的代码
以下代码为vue实现,react的话需要做一下修改,主要在于jspdf中api的调用,pdfjs几乎没有文档
当前使用的pdfjs-dist版本为 2.16.105
“pdfjs-dist”: “^2.16.105”,
<template> <div class="margin-box" v-loading="loading"> <div class="document-directory"> <span class="directory-title"> 目录 </span> <div class="directory-tree"> <!-- 目录渲染 --> <el-tree :data="treeData.outline" :default-expand-all="true" :expand-on-click-node="false" :props="treeData.defaultProps" @node-click="data=>navigateTo(data.dest)"> </el-tree> </div> </div> <!-- pdf渲染 --> <div class="PDF-content pdf-container" ref="pdfContainer"> </div> </div> </template> <script> import * as PdfJs from "pdfjs-dist/legacy/build/pdf.js"; import { getDocument } from "pdfjs-dist"; PdfJs.GlobalWorkerOptions.workerSrc = require("pdfjs-dist/build/pdf.worker.entry"); async function getPdf(src) { const loadingTask = getDocument({ url: src, disableFontFace: true, //禁用文本抗锯齿 ,提高渲染性能 }); const pdf = await loadingTask.promise; return pdf; } export default { name: "PdfViewer", props: { src: String, }, data() { return { pdf: null, numPages: 0, treeData:{ outline:[], defaultProps:{ children: 'items', label: 'title' } }, loading:false, fragment: document.createDocumentFragment() }; }, mounted() { this.loadPdf(this.src); }, methods: { async loadPdf(src) { this.loading = true // 注意,此处接受的src必须是浏览器可以直接访问pdf的路径 const pdf = await getPdf(src); this.pdf = pdf; // 获取pdf的也页数 this.numPages = pdf.numPages; this.renderAllPages(); this.renderOutline(pdf); }, //渲染整个pdf async renderAllPages() { for (let i = 1; i <= this.numPages; i++) { // 通过文档碎片统一渲染提高性能 // 插入每一页的pdf渲染 this.fragment.appendChild(await this.renderPage(i)); } this.$refs.pdfContainer.appendChild(this.fragment); this.loading = false }, // 获取目录树 async renderOutline(pdf) { const outline = await pdf.getOutline(); this.treeData.outline = outline }, // 渲染某页页pdf async renderPage(pageNumber) { if (!this.pdf) { return; } const page = await this.pdf.getPage(pageNumber); // 设置清晰度 const viewport = page.getViewport({ scale: 2 }); const canvas = document.createElement("canvas"); canvas.id = `page-${pageNumber}`; canvas.width = viewport.width; canvas.height = viewport.height; const ctx = canvas.getContext("2d"); const renderContext = { canvasContext: ctx, viewport: viewport, }; await page.render(renderContext).promise; return canvas }, // 点击跳转到某页 async navigateTo(dest) { if (!this.pdf || !dest) { return; } // 获取目标页面的页码 const pageNumber = (await this.pdf.getPageIndex(dest[0])) + 1; // 滚动到目标页面 const pageElement = document.getElementById(`page-${pageNumber}`); if (pageElement) { pageElement.scrollIntoView({ behavior: "smooth" }); } }, }, }; </script> <style scoped> .margin-box { box-sizing: border-box; margin: 10px; height: 95%; display: flex; } .document-directory { width: 400px; background-color: #fff; height: 100%; padding: 20px; overflow: auto; } .directory-title { display: inline-block; font-size: 18px; margin-bottom: 20px; } .PDF-content { flex: 1; height: 100%; overflow: auto; background: #eff; display: flex; flex-direction: column; margin:0 10px; } /deep/.el-tree-node.is-current > .el-tree-node__content { color: #cc2722 !important; font-weight: 600; } /deep/.el-tree-node:focus>.el-tree-node__content{ background-color:#fff !important; } /deep/.el-tree-node__content:hover{ background-color:#fff !important; } /deep/.el-icon-caret-right:before { content: ""; } /deep/.el-tree-node__content{ height: 40px; } /deep/.el-tree-node__label{ line-height: 40px; } </style>
复制
复制