vue实现一个pdf在线预览,pdf选择文本并提取复制文字触发弹窗效果

TOC

一、文件预览

1、安装依赖包

这里安装了disjs-dist@2.16版本,安装过程中报错缺少worker-loader

npm i pdfjs-dist@2.16.105 worker-loader@3.0.8

2、模板部分

复制代码
<template>
  <div id="pdf-view">
    <canvas v-for="page in pdfPages" :key="page" :id="pdfCanvas" />
    <div id="text-view"></div>
  </div>
</template>

3、js部分(核心)

核心代码如下:

  1. 利用 PDF.getDocument获取pdf基础数据

  2. 通过canvas将pdf渲染到canvas画布上

可能出现的问题:

(1) 页面文字可选中,但文本不可见

通过测试发现,将 pdfjs-dist/web/pdf_viewer.css 路径下的 color 属性注释后可显示文本。

复制代码
.textLayer span,
.textLayer br {
  /* color: transparent; */
  position: absolute;
  white-space: pre;
  cursor: text;
  transform-origin: 0% 0%;
}
pdf多页面处理
  1. 模板处理id作为唯一标识

  2. 修改canvas渲染逻辑,主要通过递归的方式逐一渲染

    renderPage(num) {
    this.pdfDoc.getPage(num).then((page) => {
    const canvas = document.getElementById(page-${num});
    const ctx = canvas.getContext("2d");
    const viewport = page.getViewport({ scale: this.pdfScale });
    canvas.width = viewport.width;
    canvas.height = viewport.height;
    const renderContext = {
    canvasContext: ctx,
    viewport,
    };
    page.render(renderContext);
    if (num < this.pdfPages) {
    this.renderPage(num + 1);
    }
    });
    },

二、文本选中与弹窗(核心代码)

复制代码
   Promise.all([getTextContentPromise, renderPagePromise])
            .then(([textContent]) => {
              const textLayerDiv = document.createElement("div");
              // 注意:此处不要修改该元素的class名称,该元素的样式通过外部导入,名称是固定的
              textLayerDiv.setAttribute("class", "textLayer");
              // 设置容器样式
              textLayerDiv.setAttribute(
                "style",
                `
                  z-index: 1;
                  opacity: .2;
                  // background-color:#fff;
                  // transform: scale(1.1);
                  width: 100%,
                  height: 100%,
              `,
              );
              // 设置容器的位置和宽高
              textLayerDiv.style.left = canvas.offsetLeft + "px";
              textLayerDiv.style.top = canvas.offsetTop + "px";
              textLayerDiv.style.height = canvas.offsetHeight + "px";
              textLayerDiv.style.width = canvas.offsetWidth + "px";

              const textView = document.querySelector("#text-view");
              textView.appendChild(textLayerDiv);

              const textLayer = new TextLayerBuilder({
                // container: ,
                textLayerDiv: textLayerDiv,
                pageIndex: page.pageIndex,
                viewport: viewport,
                eventBus,
                // textDivs: []
              });

              textLayer.setTextContent(textContent);
              textLayer.render();
              // 当选择文本后鼠标取消点击时触发
              textLayerDiv.addEventListener("mouseup", () => {
                // // 隐藏文本层
                // textLayerDiv.style.display = 'none';
                // 是否选择了文本
                const isTextSelected =
                  window.getSelection().toString().trim() !== "";
                if (isTextSelected) {
                  //选择的文本内容
                  const selectedText = window.getSelection().toString();
                  console.log("Selected text:", selectedText);
                  if (selectedText) {
                    alert(selectedText);
                  }
                }
              });
            })
            .catch((error) => {
              console.error("Error rendering page:", error);
            });

三、完整代码如下

复制代码
<template>
  <div id="pdf-view">
    <canvas v-for="page in pdfPages" :key="page" :id="`page-${page}`" />
    <div id="text-view"></div>
  </div>
</template>

<script>
  import * as pdfjsViewer from "pdfjs-dist/web/pdf_viewer.js";
  import "pdfjs-dist/web/pdf_viewer.css";
  import * as PDF from "pdfjs-dist/webpack";
  // import { getDocument } from 'pdfjs-dist/webpack';
  import { TextLayerBuilder } from "pdfjs-dist/web/pdf_viewer.js";
  const pdfjsWorker = import("pdfjs-dist/build/pdf.worker.entry");
  PDF.GlobalWorkerOptions.workerSrc = pdfjsWorker;
  const eventBus = new pdfjsViewer.EventBus();
  export default {
    name: "",
    components: {},
    data() {
      return {
        pdfPages: 1,
        pdfPath: "http://localhost:8080/qfnext.pdf",
        // 总页数
        pdfPages: 1,
        // 页面缩放
        pdfScale: 1,
        pdfDoc: null,
      };
    },
    mounted() {
      this.loadFile(this.pdfPath);
    },
    methods: {
      loadFile(url) {
        PDF.getDocument({
          url,
          cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/",
          cMapPacked: true,
        }).promise.then((pdf) => {
          this.pdfDoc = pdf;
          // 获取pdf文件总页数
          this.pdfPages = pdf.numPages;
          this.$nextTick(() => {
            this.renderPage(1); // 从第一页开始渲染
          });
        });
      },
      renderPage(num) {
        this.pdfDoc.getPage(num).then((page) => {
          const canvas = document.getElementById(`page-${num}`);
          const ctx = canvas.getContext("2d");
          const viewport = page.getViewport({ scale: this.pdfScale });
          canvas.width = viewport.width;
          canvas.height = viewport.height;
          const renderContext = {
            canvasContext: ctx,
            viewport,
          };

          // 获取文本内容和渲染页面的 Promise
          const getTextContentPromise = page.getTextContent();
          const renderPagePromise = page.render(renderContext);
          if (num < this.pdfPages) {
            this.renderPage(num + 1);
          }
          Promise.all([getTextContentPromise, renderPagePromise])
            .then(([textContent]) => {
              const textLayerDiv = document.createElement("div");
              // 注意:此处不要修改该元素的class名称,该元素的样式通过外部导入,名称是固定的
              textLayerDiv.setAttribute("class", "textLayer");
              // 设置容器样式
              textLayerDiv.setAttribute(
                "style",
                `
                  z-index: 1;
                  opacity: .2;
                  // background-color:#fff;
                  // transform: scale(1.1);
                  width: 100%,
                  height: 100%,
              `,
              );
              // 设置容器的位置和宽高
              textLayerDiv.style.left = canvas.offsetLeft + "px";
              textLayerDiv.style.top = canvas.offsetTop + "px";
              textLayerDiv.style.height = canvas.offsetHeight + "px";
              textLayerDiv.style.width = canvas.offsetWidth + "px";

              const textView = document.querySelector("#text-view");
              textView.appendChild(textLayerDiv);

              const textLayer = new TextLayerBuilder({
                // container: ,
                textLayerDiv: textLayerDiv,
                pageIndex: page.pageIndex,
                viewport: viewport,
                eventBus,
                // textDivs: []
              });

              textLayer.setTextContent(textContent);
              textLayer.render();
              // 当选择文本后鼠标取消点击时触发
              textLayerDiv.addEventListener("mouseup", () => {
                // // 隐藏文本层
                // textLayerDiv.style.display = 'none';
                // 是否选择了文本
                const isTextSelected =
                  window.getSelection().toString().trim() !== "";
                if (isTextSelected) {
                  //选择的文本内容
                  const selectedText = window.getSelection().toString();
                  console.log("Selected text:", selectedText);
                  if (selectedText) {
                    alert(selectedText);
                  }
                }
              });
            })
            .catch((error) => {
              console.error("Error rendering page:", error);
            });
        });
      },
    },
  };
</script>
<style lang="scss" scoped>
  .pdf-con {
    border: 2px solid #ccc;
    width: 80%;
    margin: auto;
    height: 800px;
    overflow: auto;
    // display: none;
  }
</style>
相关推荐
Ashley的成长之路43 分钟前
前端性能优化实战手册·第2篇:资源加载策略全解
前端·性能优化·资源加载·http/3·性能优化实战·资源加载优化
浅水壁虎1 小时前
vue基础(第二章 )
前端·javascript·vue.js
界面开发小八哥1 小时前
界面控件DevExtreme v26.1新版亮点——支持Angular 22
前端·javascript·angular.js·devexpress·ui开发·devextreme
Getflare1 小时前
前端 + UI 设计 + AI:这不是三个工种,是一个新三角能力模型(附自检清单)
前端·人工智能·ui
oil欧哟1 小时前
我做了一个 Vibe Coding 术语学习站:VibeHub
前端·ai·agent·独立开发·vibe coding
审小匠OpenCPAi2 小时前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
Revolution612 小时前
一个公共表格组件,是怎么一步步失控的
前端·前端工程化
腻害兔2 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:CRM 客户关系模块深度解析——从线索到回款,一套完整的 B2B 销售闭环是怎么搭的?
java·前端·javascript·vue.js·产品经理·ai编程
whyfail2 小时前
前端学 Spring Boot(2):一次点击,如何穿过整个后端?
前端·spring boot·后端