v3通过pdfjs-dist插件渲染后端返回的pdf文件流,并实现缩放、下一页

功能介绍:

  • 通过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-", "")
      );
    }
  }
};
相关推荐
万少2 分钟前
万少用9个AI工具,帮朋友完成了一个"不可能"的项目
前端
小小小小宇4 分钟前
Vue `import` 为什么可以异步加载
前端
WMYeah9 分钟前
【无标题】
前端·rust·抽奖程序·跨平台抽奖程序
Unbelievabletobe10 分钟前
免费外汇api的响应时间在不同时段下的波动分析
大数据·开发语言·前端·python
大哥,带带弟弟19 分钟前
Grafana 前端嵌入与 JWT 鉴权实战
前端·grafana
小小小小宇20 分钟前
前端 V8 引擎垃圾回收机制与内存问题排查
前端
前端老石人31 分钟前
CSS 值定义语法
前端·css
sheeta199841 分钟前
Vue 前端基础笔记
前端·vue.js·笔记
小小小小宇41 分钟前
GitLab + GitLab Runner + Qiankun 微前端 + Nginx + Node 中间件 前端开发机从零搭建 CI/CD 全流程
前端
前端那点事1 小时前
别再写垃圾组件!Vue3 如何设计「真正可复用」的高质量通用组件
前端·vue.js