功能介绍:
- 通过pdfjs-dist插件来实现将后端接口返回的pdf文件流渲染到页面
- 对页面中的pdf来进行放大、缩小、上一页、下一页或指定滚动到目标页码
1.下载pdfjs-dist插件
npm i pdfjs-dist
2.在vue文件中导入并使用
javascript
import * as pdfjsLib from "pdfjs-dist/build/pdf";
3.定义html结构
html
<div class="pdfContainer flex1" id="videoContainer">
<div class="pdfHeander flex">
<el-button
class="ml10"
size="small"
@click="prev"
:disbled="currentPage < 2"
>上一页</el-button
>
<span class="ml10">{{ currentPage }} / {{ pdfPages }}页</span>
<el-button
class="ml10"
size="small"
@click="next"
:disbled="currentPage == pdfPages"
>下一页</el-button
>
<el-button
class="ml10"
size="small"
@click="zoomPdf(false)"
:disabled="pdfScale <= 1"
>缩小</el-button
>
<el-button
class="ml10"
size="small"
@click="zoomPdf(true)"
:disabled="pdfScale > 1.6"
>放大</el-button
>
</div>
<!--此处根据pdf的页数动态生成相应数量的canvas画布-->
<div class="pdfDetail" @scroll="handleScrollPdf">
<div
v-for="pageIndex in pdfPages"
:key="pageIndex"
id="pdf-page-wrapper"
:class="`pdf-page-wrapper-${pageIndex}`"
>
<div :id="'svgContainer' + pageIndex" class="svgContainer">
<!-- SVG 矩形将在这里被动态生成 -->
</div>
<!-- pdf每一页 -->
<canvas :id="`pdf-canvas-` + pageIndex" class="pdf-canvas"></canvas>
</div>
</div>
</div>
4.调后端接口获取pdf
javascript
const handleGetPdf = () => {
axios({
url: "你的url地址",
method: "post",
responseType: "blob",
}).then((res) => {
let url = URL.createObjectURL(
new Blob([res.data], { type: "application/pdf" })
);
loadFile(url);
});
};
5.定义所需字段,渲染pdf,并获取pdf信息
- pdfjsLib.GlobalWorkerOptions.workerSrc指定 Web Worker 文件的路径
- 避免cdn链接无法访问,可以直接把node_modules/pdfjs-dist/build/pdf.worker.min.mjs文件复制一份到public下后,直接使用/pdf.worker.min.mjs
javascript
let pdfDoc = reactive({}); // 保存加载的pdf文件流
let pdfPages = ref(0); // pdf文件的页数
let pdfUrl = ref(""); //pdf文件链接
let pdfScale = ref(1.6); // 缩放比例
const isMount = ref(false); // 是否初次渲染pdf
const currentPage = ref(1); // 记录pdf当前页码
const currentRect = ref([]); // 用户记录用户当前选中的rect框数据(跨页框选时为2条)
const rectangles = ref([{ // 我的每一页pdf下的svg红框左边点数据
pageNum: 51,
positionInfo: [
{
position: [110.93, 120.81, 47.81, 110.25],
associatedPrevPage: false, // 当前rect框是否跟前后页码指向同一数据,'0'标识跟下一页第一个对象指向同一数据,'1'表示跟上一页最后一个对象指向同一数据
availDispFlag: 0,
dataInfoList: [], // 右侧需要跟pdf上每个框框进行联动的表数据
id: "864da826-a188-11ef-bbb1-0050568b195a",
}]}])
//获取pdf文档流与pdf文件的页数
const loadFile = async (url) => {
pdfjsLib.GlobalWorkerOptions.workerSrc =
// "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.7.76/pdf.worker.min.mjs";
// '/node_modules/pdfjs-dist/build/pdf.worker.min.mjs'
"/pdf.worker.min.mjs";
const loadingTask = pdfjsLib.getDocument(url); // 根据URL创建加载任务
loadingTask.promise.then((pdf) => {
pdfDoc = pdf;
pdfPages.value = pdf.numPages;
nextTick(() => {
renderPage(1);
});
});
};
6.渲染pdf文件
javascript
//渲染pdf文件
const renderPage = (num, isZoom) => {
pdfDoc.getPage(num).then((page) => {
const canvasId = "pdf-canvas-" + num;
const canvas = document.getElementById(canvasId);
const pdfWrapper = document.getElementsByClassName(
`pdf-page-wrapper-${num}`
)[0];
const ctx = canvas.getContext("2d");
const dpr = window.devicePixelRatio || 1;
const bsr =
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1;
const ratio = dpr / bsr;
const viewport = page.getViewport({ scale: pdfScale.value });
canvas.width = viewport.width * ratio;
canvas.height = viewport.height * ratio;
// pdfWrapper
canvas.style.width = pdfWrapper.style.width = viewport.width + "px";
canvas.style.height = pdfWrapper.style.height = viewport.height + "px";
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
const renderContext = {
canvasContext: ctx,
viewport: viewport,
};
page.render(renderContext);
drawRectanglesOnPage(num, viewport);
if (num < pdfPages.value) {
return renderPage(num + 1, isZoom);
}
// 缩放页面后设置当前页在最上面
isZoom && scrollToPage(true);
loading.value = false;
if (isMount.value) return;
// isMount控制页面pdf初次渲染完后滚动到第一个有数据的页面
isMount.value = true;
currentPage.value = rectangles.value[0].pageNum;
document.querySelector(".pdfDetail").scrollTop =
document.querySelector(`.pdf-page-wrapper-${currentPage.value}`)
.offsetTop - 35;
});
};
6.在每一页pdf上覆盖svg,并在svg上绘制rect矩形框
javascript
// 绘制svg矩形
function drawRectanglesOnPage(num, viewport) {
const container = document.getElementById(`svgContainer${num}`);
container.innerHTML = ""; // 清空之前的 SVG 元素
let positionInfo = rectangles.value.find(
(val) => val.pageNum == num
)?.positionInfo;
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
svg.setAttribute("x", "0");
svg.setAttribute("y", "0");
svg.setAttribute("width", viewport.width);
svg.setAttribute("height", viewport.height);
if (!positionInfo?.length) return;
positionInfo.forEach((rect) => {
// 控制rect缩放
!rect.initPosition &&
(rect.initPosition = JSON.parse(JSON.stringify(rect.position)));
rect.position = rect.initPosition.map((i) => {
return i * pdfScale.value;
});
// 判断是否展示availDispFlag===1
const rectElement = document.createElementNS(svgNS, "rect");
rectElement.setAttribute("x", rect.position[0]);
rectElement.setAttribute("y", rect.position[1]);
rectElement.setAttribute("width", rect.position[2]);
rectElement.setAttribute("height", rect.position[3]);
rectElement.setAttribute("fill", "transparent");
rectElement.setAttribute(
"stroke",
"red"
);
rectElement.setAttribute("stroke-width", "1");
rectElement.setAttribute("xyId", rect.xyId);
svg.appendChild(rectElement);
container.appendChild(svg);
});
}
7.分页、缩放方法
javascript
// 上一页
const prev = () => {
if (currentPage.value <= 1) {
return;
}
currentPage.value -= 1;
scrollToPage();
};
// 下一页
const next = () => {
if (currentPage.value >= pdfPages.value) {
return;
}
currentPage.value += 1;
scrollToPage();
};
// 滚动pdf到指定页
const scrollToPage = (bol) => {
nextTick(() => {
document.querySelector(".pdfDetail").scrollTop =
document.querySelector(`.pdf-page-wrapper-${currentPage.value}`)
.offsetTop - 35;
if (bol && activeId.value) {
document
.querySelector(`[xyId="${activeId.value}"]`)
.setAttribute("fill", "rgba(177, 221, 94, 0.2)");
}
});
};
// 缩放pdf
const zoomPdf = (bol) => {
pdfScale.value += bol ? 0.2 : -0.2;
renderPage(1, true);
};
// 滚动pdf时记录当前页码
const handleScrollPdf = (e) => {
for (let dom of document.querySelectorAll("#pdf-page-wrapper")) {
if (
Math.abs(e.target.scrollTop - dom.offsetTop + 35) <
dom.clientHeight / 2
) {
currentPage.value = Number(
dom.className.replace("pdf-page-wrapper-", "")
);
}
}
};