【PDF.js】2023 最新 PDF.js 在 Vue3 中的使用

因为自己写业务要定制各种 pdf 预览情况(可能),所以采用了 pdf.js 而不是各种第三方封装库,主要还是为了更好的自由度。

一、PDF.js 介绍

官方地址
中文文档

PDF.js 是一个使用 HTML5 构建的便携式文档格式查看器。
pdf.js 是社区驱动的,并由 Mozilla 支持。我们的目标是为解析和呈现 PDF 创建一个通用的、基于 Web 标准的平台。

二、 安装方法

1、下载 pdf.js

下载地址
在这里插入图片描述
我下载的版本是 pdfjs-4.0.189-dist
在这里插入图片描述

2、解压包并放到项目中

解压后将完整文件夹放到 vue3 的 public 文件夹内
在这里插入图片描述

3、屏蔽跨域错误,允许跨域

web/viewer.mjs 内找到搜索 throw new Error("file origin does not match viewer's") 并注释掉,如果不注释,可能会出现跨域错误,无法正常预览文件
在这里插入图片描述
这样就算安装完成了,后面我们开始在项目中使用。

三、基础使用

1、创建 PDF 组件

我们可以创建一个 PDF 组件,代码如下:

<script setup lang="ts">
import { onMounted, ref } from 'vue';
interface Props {
  url: string; // pdf文件地址
}
const props = defineProps<Props>();
const pdfUrl = ref(''); // pdf文件地址
const fileUrl = '/pdfjs-4.0.189-dist/web/viewer.html?file='; // pdfjs文件地址

onMounted(() => {
  // encodeURIComponent() 函数可把字符串作为 URI 组件进行编码。
  // 核心就是将 iframe 的 src 属性设置为 pdfjs 的地址,然后将 pdf 文件的地址作为参数传递给 pdfjs
  // 例如:http://localhost:8080/pdfjs-4.0.189-dist/web/viewer.html?file=http%3A%2F%2Flocalhost%3A8080%2Fpdf%2Ftest.pdf
  pdfUrl.value = fileUrl + encodeURIComponent(props.url);
});
</script>

<template>
  <div class="container">
    <iframe :src="pdfUrl" width="100%" height="100%"></iframe>
  </div>
</template>

<style scoped lang="scss">
.container {
  width: 100%;
  height: 100%;
}
</style>

2、使用组件

比如我们需要预览 public 下的一个 test.pdf 文件

<div class="pdf-box">
  <PDF url="/public/test.pdf" />
</div>

下面是界面默认预览效果
在这里插入图片描述

四、进阶使用

1、页面跳转(传参)

比如我们要跳到第 10 页,我们可以在地址里面添加参数 &page=${10}

pdfUrl.value = fileUrl + encodeURIComponent(props.url) + `&page=${10}`;

viewer.mjs 找到 setInitialView 函数,注意是下面这个:
在这里插入图片描述
重点:在函数末尾最下面添加下面的跳转代码(写在上面会报错,因为还没有获取到实例)

    console.log(this.pdfViewer);
    // 获取url参数
    function getQueryVariable(variable) {
      var query = window.location.search.substring(1);
      var vars = query.split('&');
      for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split('=');
        if (pair[0] == variable) {
          return pair[1];
        }
      }
      return false;
    }
    // 跳转到指定页
    const page = getQueryVariable('page');
    console.log(page);
    if (page) {
      this.pdfViewer.currentPageNumber = Number(page);
    }

2、文本标注(传参)

某些时候我们需要跳转到指定页面,然后自动标注文本,这个时候就需要自动标注了
在这里插入图片描述
代码跟跳转一样,写在后面就可以了

    // 自动高亮文本(要解码)decodeURIComponent: 解码
    const markText = decodeURIComponent(getQueryVariable('markText'));
    console.log('markText===>', markText);
    if (markText) {
      // 对查询输入框进行赋值
      document.getElementById('findInput').value = markText;
      // 点击高亮按钮实现高亮显示关键词
      document.getElementById('findHighlightAll').click();
    }

目前我还没有找到批量标注的办法,批量标注建议还是使用下面页面+坐标,遮罩的方法
在这里插入图片描述

3、添加遮罩高亮(页码+坐标)

主要是为了解决批量标注的问题,因为 pdfjs 原生只支持单文本,不支持批量,要修改大量源码(我能力不行,太难了😥)

所以还是换了种方案,就是后端返回页码+坐标,添加遮罩层渲染的方式。

这种方法主要是找到渲染的 dom元素,因为渲染的pdf有一个叫做 data-page-number="1" 的属性,因此我们可以通过 js 的 querySelectorAll 选择器找到对应属性的 dom 元素,然后再操作添加遮罩就可以了,代码放在下面。
在这里插入图片描述

    // 测试的坐标
    const content_pos_1 = {
      x: 0.5135954145019941,
      y: 0.4662730487881233,
    };
    const content_pos_2 = {
      x: 0.7135954145019941,
      y: 0.8662730487881233,
    };
    // 查找属性 data-page-number='页码' 的 dom 元素
    const pageList = document.querySelectorAll(`[data-page-number='${page}']`);
    console.log('查询到的dom列表===>\n', pageList[1]);
    // 查询到的第一个是左侧小菜单页码div,第二个是才是展示的div
    const pageView = pageList[1];
    console.log('右侧展示的dom===>\n', pageView);
    // 在元素上画一个div
    const div = document.createElement('div');
    div.style.width = (content_pos_2.x - content_pos_1.x) * 100 + '%';
    div.style.height = (content_pos_2.y - content_pos_1.y) * 100 + '%';
    div.style.backgroundColor = 'rgb(255, 255, 0, 0.1)';
    div.style.position = 'absolute';
    div.style.top = content_pos_1.y * 100 + '%';
    div.style.left = content_pos_1.x * 100 + '%';
    div.style.zIndex = '1'; // pdfjs 文本的层级是2 所以这里要设置为1 放着不能复制
    pageView.appendChild(div);

渲染到pdf上就是下面的样子:
在这里插入图片描述

4、添加遮罩高亮(缩放动态更新)

因为 pdf 会缩放的缘故,缩放的话会重新更新 pdf ,我们添加的 div 就会消失,所以我们要在重新更新的时候重新添加,源码内部重新添加的函数在这个位置: #updateUIState
在这里插入图片描述
我们只需要将修改后重新添加的代码放在尾部就行
首先我们要修改第三部分的代码

   // 测试的坐标
    const content_pos_1 = {
      x: 0.5135954145019941,
      y: 0.4662730487881233,
    };
    const content_pos_2 = {
      x: 0.7135954145019941,
      y: 0.8662730487881233,
    };
    // pdf 缩放会重新设置,所以放在window保存,其他地方要用
    window.page = page;
    window.shade = {
      width: (content_pos_2.x - content_pos_1.x) * 100 + '%',
      height: (content_pos_2.y - content_pos_1.y) * 100 + '%',
      top: content_pos_1.y * 100 + '%',
      left: content_pos_1.x * 100 + '%',
    };
    console.log(window.shade);
    // 查找属性 data-page-number='页码' 的 dom 元素
    const pageList = document.querySelectorAll(`[data-page-number='${page}']`);
    console.log('查询到的dom列表===>\n', pageList[1]);
    // 查询到的第一个是左侧小菜单页码div,第二个是才是展示的div
    const pageView = pageList[1];
    console.log('右侧展示的dom===>\n', pageView);
    // 在元素上画一个div
    const div = document.createElement('div');
    div.id = 'shade';
    div.style.width = window.shade.width;
    div.style.height = window.shade.height;
    div.style.backgroundColor = 'rgb(255, 255, 0, 0.1)';
    div.style.position = 'absolute';
    div.style.top = window.shade.top;
    div.style.left = window.shade.left;
    div.style.zIndex = '1';
    pageView.appendChild(div);

然后在 #updateUIState 函数的末尾添加下面的新增代码

    setTimeout(() => {
      if (!window.page) return;
      const pageList = document.querySelectorAll(`[data-page-number='${window.page}']`);
      const pageView = pageList[1];
      // 删除 id 为 shade 的元素(旧遮罩)
      const shade = document.getElementById('shade');
      if (shade) {
        shade.remove();
      }
      const div = document.createElement('div');
      div.id = 'shade';
      div.style.width = window.shade.width;
      div.style.height = window.shade.height;
      div.style.backgroundColor = 'rgb(255, 255, 0, 0.1)';
      div.style.position = 'absolute';
 	  div.style.top = window.shade.top;
      div.style.left = window.shade.left;
      div.style.zIndex = '1';
      pageView.appendChild(div);
    }, 500);

最终效果如下:
在这里插入图片描述
ps:如果要做大量的页面+坐标渲染(后端返回的是个数组),修改下上面的代码逻辑就行,传参自己写,不难的

当然,也可以看下面的代码哈哈哈,我还是写出来吧

5、添加遮罩高亮(数组批量跨页渲染)

假设后端返回的数据格式是这样的,是一个包含 页码、坐标的标注数组,我们需要在每个页码内渲染遮罩
在这里插入图片描述

我们就需要这样传参
在这里插入图片描述
setInitialView(storedHash, { rotation, sidebarView, scrollMode, spreadMode } = {}) 初始化函数中:

    window.content_pos = JSON.parse(decodeURIComponent(getQueryVariable('content_pos')));
    console.log(window.content_pos[0]);
    window.content_pos.forEach((item, index) => {
      const page = item.page_no;
      const shade = {
        width: (item.right_bottom.x - item.left_top.x) * 100 + '%',
        height: (item.right_bottom.y - item.left_top.y) * 100 + '%',
        top: item.left_top.y * 100 + '%',
        left: item.left_top.x * 100 + '%',
      };
      console.log(shade);
      const pageList = document.querySelectorAll(`[data-page-number='${page}']`);
      const pageView = pageList[1];
      const div = document.createElement('div');
      div.id = 'shade' + index;
      div.style.width = shade.width;
      div.style.height = shade.height;
      div.style.backgroundColor = 'rgb(255, 255, 0, 0.1)';
      div.style.position = 'absolute';
      div.style.top = shade.top;
      div.style.left = shade.left;
      div.style.zIndex = '1';
      pageView.appendChild(div);
    });

#updateUIState(resetNumPages = false) 更新函数中:

    setTimeout(() => {
      if (window.content_pos) {
        window.content_pos.forEach((item, index) => {
          const shadeEl = document.getElementById('shade' + index);
          if (shadeEl) {
            shadeEl.remove();
          }
          const page = item.page_no;
          const shade = {
            width: (item.right_bottom.x - item.left_top.x) * 100 + '%',
            height: (item.right_bottom.y - item.left_top.y) * 100 + '%',
            top: item.left_top.y * 100 + '%',
            left: item.left_top.x * 100 + '%',
          };
          const pageList = document.querySelectorAll(`[data-page-number='${page}']`);
          const pageView = pageList[1];
          const div = document.createElement('div');
          div.id = 'shade' + index;
          div.style.width = shade.width;
          div.style.height = shade.height;
          div.style.backgroundColor = 'rgb(255, 255, 0, 0.1)';
          div.style.position = 'absolute';
          div.style.top = shade.top;
          div.style.left = shade.left;
          div.style.zIndex = '1';
          pageView.appendChild(div);
        });
      }
    }, 500);

效果展示,可以实现跨页,多页渲染
在这里插入图片描述

后续根据开发业务持续更新😁

感谢大佬们的无私分享

详细|vue中使用PDF.js预览文件实践
vue3项目使用pdf.js插件实现:搜索高亮、修改pdf.js显示的页码、向pdf.js传值、控制搜索、处理接口文件流
pdf.js根据路径里传参数高亮显示关键字(跳转到对应页面)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【HuggingFace Transformer库学习笔记】基础组件学习:pipeline

一、Transformer基础知识 pip install transformers datasets evaluate peft accelerate gradio optimum sentencepiece pip install jupyterlab scikit-learn pandas matplotlib tensorboard nltk rouge在host文件里添加途中信息&#xff0c;可以避免运行代码下载模型时候报错…

vue中下载文件后无法打开的坑

今天在项目开发的时候临时要添加个导出功能我就写了一份请求加导出得代码&#xff0c; 代码&#xff1a; //导出按钮放开exportDutySummarizing (dataRangeInfo) {const params {departmentName: dataRangeInfo.name,departmentQode: dataRangeInfo.qode}//拼接所需得urlcons…

Tomcat注册为服务后,如何配置Tomcat内存大小

前提条件&#xff1a;tomcat已经注册为服务。 1.winR,输入regedit打开注册表 2.找到Tomcat注册表路径&#xff1a; HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Apache Software Foundation\Procrun 2.0\Tomcat80603.找到jvm内存配置路径&#xff1a; HKEY_LOCAL_MACHINE\SOFTW…

Redis高可用之主从复制及哨兵模式

一、Redis的主从复制 1.1 Redis主从复制定义 主从复制是redis实现高可用的基础&#xff0c;哨兵模式和集群都是在主从复制的基础之上实现高可用&#xff1b; 主从复制实现数据的多级备份&#xff0c;以及读写分离(主服务器负责写&#xff0c;从服务器只能读) 1.2 主从复制流…

Windows从源码构建tensorflow

由一开始的在线编译&#xff0c;到后面的离线编译&#xff0c;一路踩坑无数。在此记录一下参考过的文章&#xff0c;有时间整理一下踩坑记录。 一、环境配置 在tensorflow官网上有版本对应关系 win10 bazel 3.1.0 msys2 tensorflow2.3.0 python3.5-3.8 MSVC2019 protobuf3.9.…

羊大师:强健身体是成功的关键

健康是一项无价的财富&#xff0c;拥有强健的身体是实现人生目标的关键。而如何保持健康并拥有一个强健的身体呢&#xff1f;下面就为大家分享一些有效的健身方法和建议&#xff0c;帮助您达到健美身材的目标。 良好的饮食习惯是形成强健身体的基石。我们要摄入足够的营养物质…

Java二级医院区域HIS信息管理系统源码(SaaS服务)

一个好的HIS系统&#xff0c;要具有开放性&#xff0c;便于扩展升级&#xff0c;增加新的功能模块&#xff0c;支撑好医院的业务的拓展&#xff0c;而且可以反过来给医院赋能&#xff0c;最终向更多的患者提供更好的服务。 系统采用前后端分离架构&#xff0c;前端由Angular、J…

首先啊骚年们我们必须先了解网络安全这个行业究竟是干啥的。

导 读 近年来&#xff0c;人工智能、5G、量子信息技术、工业互联网、大数据、云计算、物联网、虚拟现实、区块链等具有颠覆性的战略性新技术突飞猛进&#xff0c;但伴随着互联网技术的发展&#xff0c;网络安全问题也日趋多样化&#xff0c;甚至严重威胁到国家、企业&#xff…

【正点原子STM32连载】 第六十章 串口IAP实验(Julia分形)实验 摘自【正点原子】APM32F407最小系统板使用指南

1&#xff09;实验平台&#xff1a;正点原子APM32F407最小系统板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html## 第六十…

AD9528学习笔记

前言 AD9528是ADI的一款时钟芯片&#xff0c;由2-stage PLL组成&#xff0c;并且集成JESD204B/JESD204C SYSREF信号发生器&#xff0c;SYSREF发生器输出单次、N次或连续信号&#xff0c;并与PLL1和PLL2输出同步&#xff0c;从而可以实现多器件之间的同步。 AD9528总共有14路输…

<精学社>LCD1602移屏操作

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…

Linux:配置Ubuntu系统的镜像软件下载地址

一、原理介绍 好处&#xff1a;从国内服务器下载APT软件&#xff0c;速度快。 二、配置 我这里配置的是清华大学的镜像服务器地址 https://mirrors.tuna.tsinghua.edu.cn/ 1、备份文件 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak2、清空sources.list ec…

CS5511规格书|CS5511方案应用说明|DP转双路LVDS/eDP芯片方案

概述&#xff1a;CS5511是一个将DP/eDP输入转换为LVDS信号的桥接芯片&#xff0c;此外&#xff0c;CS5511可以用作在DP/eDP输入到DP/eDP输出场景中桥接芯片。CS5511的高级接收器支持VEDA DisplayPort&#xff08;DP&#xff09;1.3和嵌入式DisplayPort&#xff08;eDP&#xf…

这是一个最简单的爱国主义为主题的网页首页

代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head> <meta charset"UTF-8"> <title>爱国主题网页</title> <style> body { font-family: Arial, sans-serif; …

pytest系列——pytest-instafail插件之命令行实时输出错误信息

前言 1、pytest 运行全部用例的时候&#xff0c;在控制台会先显示用例的运行结果(.或F)&#xff1b;等待用例全部运行完成后最后把报错信息全部一起抛出到控制台。 2、这样我们每次都需要等用例运行结束&#xff0c;才知道为什么报错&#xff0c;不方便实时查看报错信息。 3…

数据挖掘之PCA-主成分分析

PCA的用处&#xff1a;找出反应数据中最大变差的投影&#xff08;就是拉的最开&#xff09;。 在减少需要分析的指标同时&#xff0c;尽量减少原指标包含信息的损失&#xff0c;以达到对所收集数据进行全面分析的目的 但是什么时候信息保留的最多呢&#xff1f;具体一点&#…

Day31| Leetcode 455. 分发饼干 Leetcode 376. 摆动序列 Leetcode 53. 最大子数组和

进入贪心了&#xff0c;我觉得本专题是最烧脑的专题 Leetcode 455. 分发饼干 题目链接 455 分发饼干 让大的饼干去满足需求量大的孩子即是本题的思路&#xff1a; class Solution { public:int findContentChildren(vector<int>& g, vector<int>& s) {…

joplin笔记同步 到腾讯云S3

创建存储桶 打开腾讯云的存储桶列表&#xff0c;点击“创建存储桶”&#xff0c;输入名称&#xff0c;选择地域&#xff08;建议选择离自己较近的地域以降低访问时延&#xff09;和访问权限&#xff08;建议选择“私有读写”&#xff09;。 s3 存储桶&#xff1a; 存储桶的名称…

windows环境下使用arthas不启动服务替换文件

场景&#xff1a; windows环境&#xff0c;如果现场环境客户正在使用项目&#xff0c;如果要替换项目中的一个class文件&#xff0c;但是又不能重启服务改怎么处理&#xff0c;今天介绍使用arthas中的retransform命令动态替换及使用注意的事项。 环境&#xff1a; windows,arth…

葡萄酒怎么按照饮用时间分类?

不同的葡萄酒搭配不同的餐食&#xff0c;会让饮酒人有不一样的感受和体会&#xff0c;所以&#xff0c;葡萄酒是分场合并且有饮用时间的。云仓酒庄的品牌雷盛红酒分享一般按照饮用时间分类可以把葡萄酒分为三大类&#xff0c;分别是餐前酒、佐餐酒和餐后酒。 餐前酒&#xff1…