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-", "")
      );
    }
  }
};
相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪5 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy6 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom7 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试