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

news2025/3/9 10:54:54

[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画布上
<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";

  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(`pdfCanvas`);
          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);
        });
      },
    },
  };
</script>
可能出现的问题:

(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作为唯一标识
 <canvas v-for="page in pdfPages" :key="page" :id="`page-${page}`" />
  1. 修改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>


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2311030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时间复杂度分析与递归,以新南UNSW的COMP2521作业题为例

作者&#xff1a;Smooth&#xff08;连接教育高级讲师&#xff09; 首发于&#xff1a;⁠⁠⁠⁠⁠⁠⁠UNSW学习知识库&#xff08;UNSW Study Wiki&#xff09; 创作时间&#xff1a;2025年3月5日 如何测度算法的时间性能&#xff1f;理论分析Theoretical Analysis 测度算法时…

基于CSDN资源,搭建AI赋能农业典型场景落地方案

农业场景&#xff0c;不但是信息化、自动化等薄弱的产业&#xff0c;更是AI落地困难的场景。基于此&#xff0c;想通过这篇文章查找一个CSDN相关资源&#xff0c;论证一下AI赋能农业三个典型场景的实现思路。 场景1&#xff1a;水质-土壤智能调控 **痛点&#xff1a;**水质恶…

python量化交易——金融数据管理最佳实践——使用qteasy大批量自动拉取金融数据

文章目录 使用数据获取渠道自动填充数据QTEASY数据拉取功能数据拉取接口refill_data_source()数据拉取API的功能特性多渠道拉取数据实现下载流量控制实现错误重试日志记录其他功能 qteasy是一个功能全面且易用的量化交易策略框架&#xff0c; Github地址在这里。使用它&#x…

RoboBrain:从抽象到具体的机器人操作统一大脑模型

25年2月来自北大、北京智源、中科院自动化所等的论文“RoboBrain: A Unified Brain Model for Robotic Manipulation from Abstract to Concrete”。 目前的多模态大语言模型&#xff08;MLLM&#xff09; 缺少三项必备的机器人大脑能力&#xff1a;规划能力&#xff0c;将复杂…

DeepSeek本地接口调用(Ollama)

前言 上篇博文&#xff0c;我们通过Ollama搭建了本地的DeepSeek模型&#xff0c;本文主要是方便开发人员&#xff0c;如何通过代码或工具&#xff0c;通过API接口调用本地deepSeek模型 前文&#xff1a;DeepSeek-R1本地搭建_deepseek 本地部署-CSDN博客 注&#xff1a;本文不仅…

SQL_语法

1 数据库 1.1 新增 create database [if not exists] 数据库名; 1.2 删除 drop database [if exists] 数据库名; 1.3 查询 (1) 查看所有数据库 show databases; (2) 查看当前数据库下的所有表 show tables; 2 数据表 2.1 新增 (1) 创建表 create table [if not exists…

全面回顾复习——C++语法篇1(基于牛客网C++题库)

注&#xff1a;牛客网允许使用万能头文件#include<bits/stdc.h> 1、求类型长度——sizeof&#xff08;&#xff09;函数 2、将浮点数四舍五入——round&#xff08;&#xff09;函数——前面如果加上static_cast会更安全一些 在C语言中可以使用printf&#xff08;“.0l…

一、数据库 MySQL 基础学习 (上)

一、数据库的概念 DB 数据库&#xff08;database&#xff09;&#xff1a;存储数据的“仓库”&#xff0c;保存一系列有组织的数据 DBMS&#xff1a;数据库管理系统(Database Management System)。数据库是通过 DBMS 创建和操作的容器 创建的 DBMS&#xff1a; MySQL、Oracl…

基于Django创建一个WEB后端框架(DjangoRestFramework+MySQL)流程

一、Django项目初始化 1.创建Django项目 Django-admin startproject 项目名 2.安装 djangorestframework pip install djangorestframework 解释: Django REST Framework (DRF) 是基于 Django 框架的一个强大的 Web API 框架&#xff0c;提供了多种工具和库来构建 RESTf…

AutoGen学习笔记系列(七)Tutorial - Managing State

这篇文章瞄准的是AutoGen框架官方教程中的 Tutorial 章节中的 Managing State 小节&#xff0c;主要介绍了如何对Team内的状态管理&#xff0c;特别是如何 保存 与 加载 状态&#xff0c;这对于Agent系统而言非常重要。 官网链接&#xff1a;https://microsoft.github.io/auto…

Redis渐进式遍历数据库

目录 渐进式遍历 数据库 渐进式遍历 keys*可以一次性的把整个redis中所有key都获取到&#xff0c;这个操作是非常危险的&#xff0c;因为可能一下获取到太多的key&#xff0c;阻塞redis服务器。要想很好的获取到所有的key&#xff0c;又不想出现卡死的情况&#xff0c;就可以…

基于单片机的速度里程表设计(论文+源码)

1 系统方案 本次智能速度里程表的总体架构如图2-1所示&#xff0c;在硬件上包括了STC89C52单片机&#xff0c;电机&#xff0c;显示模块&#xff0c;报警模块&#xff0c;DS1302时钟模块&#xff0c;超速检测模块&#xff0c;按键等等。在软件设计功能的功能上&#xff0c;按下…

计算机毕业设计Python+Django+Vue3微博数据舆情分析平台 微博用户画像系统 微博舆情可视化(源码+ 文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

Dify+DeepSeek | Excel数据一键可视化(创建步骤案例)(echarts助手.yml)(文档表格转图表、根据表格绘制图表、Excel绘制图表)

Dify部署参考&#xff1a;Dify Rag部署并集成在线Deepseek教程&#xff08;Windows、部署Rag、安装Ragan安装、安装Dify安装、安装ollama安装&#xff09; DifyDeepSeek - Excel数据一键可视化&#xff08;创建步骤案例&#xff09;-DSL工程文件&#xff08;可直接导入&#x…

安装与配置 STK-MATLAB 接口

STK版本为11.6 Matlab版本为R2018a STK 提供 Connect 和 Object Model (COM) 两种接口与 MATLAB 交互&#xff0c;推荐使用 COM接口进行二次开发。 确保安装了 STK&#xff0c;并且 MATLAB 可以访问 STK Object Model。 在 MATLAB 中运行&#xff1a; % 添加 STK COM 库&#…

计算机二级MS之PPT

声明&#xff1a;跟着大猫和小黑学习随便记下一些笔记供大家参考&#xff0c;二级考试之前将持续更新&#xff0c;希望大家二级都能轻轻松松过啦&#xff0c;过了二级的大神也可以在评论区留言给点建议&#xff0c;感谢大家&#xff01;&#xff01; 文章目录 考题难点1cm25px…

python中采用opencv作常规的图片处理的方法~~~

在python中&#xff0c;我们经常会需要对图片做灰度/二值化/模糊等处理&#xff0c;这时候opencv就是我们的好帮手了&#xff0c;下面我来介绍一下相关用法: 首先&#xff0c;需要安装opencv-python库: 然后&#xff0c;在你的代码中引用: import cv2 最后就是代码了&#x…

deepseek在pycharm 中的配置和简单应用

对于最常用的调试python脚本开发环境pycharm&#xff0c;如何接入deepseek是我们窥探ai代码编写的第一步&#xff0c;熟悉起来总没坏处。 1、官网安装pycharm社区版&#xff08;免费&#xff09;&#xff0c;如果需要安装专业版&#xff0c;需要另外找破解码。 2、安装Ollama…

Redis数据结构,渐进式遍历,数据库管理

1.Redis的其他数据结构 前面我们主要讲述了Redis中比较常用的集中数据结构String&#xff0c;List&#xff0c;Hash&#xff0c;Set&#xff0c;Zset&#xff0c;但这并不代表Redis只用这几种数据结构还有如Streams&#xff0c;Geospatial&#xff0c;Hyperloglog&#xff0c;…

【够用就好006】如何从零开发游戏上架steam面向AI编程的godot独立游戏制作实录001流程

记录工作实践 这是全新的系列&#xff0c;一直有个游戏制作梦 感谢AI时代&#xff0c;让这一切变得可行 长欢迎共同见证&#xff0c;期更新&#xff0c;欢迎保持关注&#xff0c;待到游戏上架那一天&#xff0c;一起玩 面向AI编程的godot独立游戏制作流程实录001 本期是第…