Bootstrap

基于pdfjs-dist和pdf-lib实现PDF的预览(缩放、下载)

安装依赖

demo使用的vue为3.4.21,pdfjs-dist为4.3.136,pdf-lib为1.17.1。

npm i pdfjs-dist pdf-lib

创建vue组件

首先是html部分
<template>
  <div class="pdf-wrapper">
    <div ref="pdfContainer" class="pdf-container" @mousedown="onMouseDown">
      <!-- PDF的canvas将会插入到这里 -->
    </div>
    <div class="controls">
      <button @click="prevPage">上一页</button>
      <button @click="nextPage">下一页</button>
      <button @click="zoomIn">放大</button>
      <button @click="zoomOut">缩小</button>
      <button @click="downloadPDF">下载</button>
    </div>
    <div class="page-wrapper">
      当前第{{ currentPage }}页,共{{ totalPages }}页
    </div>
  </div>
</template>
js部分

其中需要注意的部分是pdfjsLib.GlobalWorkerOptions.workerSrc会查到多种写法,大多数是引人的在线js,对于内网或者无互联网环境不友好,且因为版本问题无法直接替换引入的url中的版本。demo中使用的是安装的依赖中的js。(若版本不同导致报错,可评论探讨)

import { ref, onMounted } from "vue";
import * as pdfjsLib from "pdfjs-dist";
import * as workerSrc from "pdfjs-dist/build/pdf.worker.min?url";
import { PDFDocument } from "pdf-lib";

// demo的pdf的路径是从父组件传入在onMounted中进行赋值
const props = defineProps(["src"]);

// 配置 PDF.js worker 的路径
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc.default;

const pdfContainer = ref(null); // pdf容器
const pdfUrl = ref(""); // 可替换为你的 PDF 文件路径
let pdfDoc = null; // pdf显示内容
let scale = ref(1.0); // 缩放比例
let currentPage = ref(1); // 当前页
let totalPages = ref(0); // 总页数
let isDragging = ref(false); // 是否可拖拽
let startX = ref(0); // 拖拽的x轴
let startY = ref(0); // 拖拽的y轴
let scrollLeft = ref(0); // 滚动的left
let scrollTop = ref(0); // 滚动的top

onMounted(async () => {
  //对pdf的url进行赋值
  pdfUrl.value = props.src;
  //使用pdfjs-dist对pdf进行解析,该步骤为异步
  const loadingTask = pdfjsLib.getDocument(pdfUrl.value);
  pdfDoc = await loadingTask.promise;
  //获取到总页数
  totalPages.value = pdfDoc.numPages;
  //使用渲染方法
  renderPage(currentPage.value);
});

//渲染方法()
const renderPage = async (num) => {
  const page = await pdfDoc.getPage(num);
  const viewport = page.getViewport({ scale: scale.value });
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");
  canvas.height = viewport.height;
  canvas.width = viewport.width;

  const renderContext = {
    canvasContext: context,
    viewport: viewport,
  };
  await page.render(renderContext).promise;
  pdfContainer.value.innerHTML = "";
  pdfContainer.value.appendChild(canvas);
};

//放大
const zoomIn = () => {
  scale.value += 0.1;
  renderPage(currentPage.value);
};

//缩小
const zoomOut = () => {
  if (scale.value > 0.2) {
    scale.value -= 0.1;
    renderPage(currentPage.value);
  }
};

//下一页
const nextPage = () => {
  if (currentPage.value < totalPages.value) {
    currentPage.value += 1;
    renderPage(currentPage.value);
  }
};

//上一页
const prevPage = () => {
  if (currentPage.value > 1) {
    currentPage.value -= 1;
    renderPage(currentPage.value);
  }
};

//下载
const downloadPDF = async () => {
  const existingPdfBytes = await fetch(pdfUrl.value).then((res) =>
    res.arrayBuffer()
  );
  const pdfDoc = await PDFDocument.load(existingPdfBytes);
  const pdfBytes = await pdfDoc.save();
  const blob = new Blob([pdfBytes], { type: "application/pdf" });
  const link = document.createElement("a");
  link.href = window.URL.createObjectURL(blob);
  link.download = "downloaded.pdf";
  link.click();
};

//用于实现拖拽
//鼠标按下
const onMouseDown = (e) => {
  isDragging.value = true;
  startX.value = e.pageX - pdfContainer.value.offsetLeft;
  startY.value = e.pageY - pdfContainer.value.offsetTop;
  scrollLeft.value = pdfContainer.value.scrollLeft;
  scrollTop.value = pdfContainer.value.scrollTop;
  document.addEventListener("mousemove", onMouseMove);
  document.addEventListener("mouseup", onMouseUp);
};

//鼠标移动
const onMouseMove = (e) => {
  if (!isDragging.value) return;
  const x = e.pageX - pdfContainer.value.offsetLeft;
  const y = e.pageY - pdfContainer.value.offsetTop;
  const walkX = x - startX.value;
  const walkY = y - startY.value;
  pdfContainer.value.scrollLeft = scrollLeft.value - walkX;
  pdfContainer.value.scrollTop = scrollTop.value - walkY;
};

//鼠标抬起
const onMouseUp = () => {
  isDragging.value = false;
  document.removeEventListener("mousemove", onMouseMove);
  document.removeEventListener("mouseup", onMouseUp);
};
css部分
.pdf-wrapper {
  width: 100%;
  height: 100%;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.pdf-container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow: auto;
  cursor: grab;
}

.pdf-container:active {
  cursor: grabbing;
}

.controls {
  position: absolute;
  top: 10px;
  left: 10px;
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 10px;
}

button {
  padding: 5px 10px;
  cursor: pointer;
}

.page-wrapper {
  font-size: 20px;
  color: #fff;
}

demo地址

gitee地址:https://gitee.com/dasl1412/pdf-view

tips

更新一下小问题
[2024.6.28]
在实际使用时以上内容本地使用没有问题,但当部署在服务器之后(目前只是用了nginx进行部署),预览时会报错,报错如下:在这里插入图片描述
这时候可以通过在nginx.conf内一下配置,可正常使用。(nginx -t检查配置时可能会报警告,目前暂未研究,nginx比较菜,如有大神可评论区指点)

types {
        text/javascript js mjs;
}

[2024.7.9]
使用中发现了一下问题,由于篇幅问题,新写了一篇文章,若有使用该封装遇到问题的,可以移步查看是否是相似问题,若出现其他问题可以评论区讨论。(基于pdfjs-dist和pdf-lib实现PDF的预览(缩放、下载)(二)

;