页面的最终效果,
根据静态的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>