因为自己写业务要定制各种 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.pdfpdfUrl.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,第二个是才是展示的divconst pageView = pageList[1];console.log('右侧展示的dom===>\n', pageView);// 在元素上画一个divconst 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,第二个是才是展示的divconst pageView = pageList[1];console.log('右侧展示的dom===>\n', pageView);// 在元素上画一个divconst 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根据路径里传参数高亮显示关键字(跳转到对应页面)