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-", "")
      );
    }
  }
};
相关推荐
顾尘眠4 小时前
http常用状态码(204,304, 404, 504,502)含义
前端
王先生技术栈5 小时前
思维导图,Android版本实现
java·前端
悠悠:)6 小时前
前端 动图方案
前端
星陈~6 小时前
检测electron打包文件 app.asar
前端·vue.js·electron
Aatroox6 小时前
基于 Nuxt3 + Obsidian 搭建个人博客
前端·node.js
每天都要进步哦6 小时前
Node.js中的fs模块:文件与目录操作(写入、读取、复制、移动、删除、重命名等)
前端·javascript·node.js
brzhang7 小时前
开源了一个 Super Copy Coder ,0 成本实现视觉搞转提示词,效率炸裂
前端·人工智能
diaobusi-887 小时前
HTML5-标签
前端·html·html5
我命由我123458 小时前
CesiumJS 案例 P34:场景视图(3D 视图、2D 视图)
前端·javascript·3d·前端框架·html·html5·js
就是蠢啊8 小时前
封装/前线修饰符/Idea项目结构/package/impore
java·服务器·前端