1 前言
近日,后端拿着电脑来与我说:"那个代理商反馈这个pdf看图大文件放大还是有些卡,说是放大到200%以上卡顿更明显";我一想,不可能啊,我上次不是优化过了吗,怎么还卡呢,于是我说:"不会吧,上次不是优化过了吗,怎么还卡呢?我来看看"。 然后经过我的反复测试,在我的电脑上发现,每当我放大到400%以上的时候我的电脑就会异常的卡顿。
于是本着从事多年计算机行业的专业素养加上学习多年计算机专业知识的压舱石,我不假思索的在键盘上按下了ctrl+alt+delete按键,打开了任务管理器,再次测试pdf放大功能,就当我放大到400%以上,然后令人惊讶的事件发生了!我giao,内存怎么彪到100%了?我一想,这pdf放大肯定有问题啊!还好巧不巧,只有当我放大到400%以上才出现这个问题。
经过我数天的排查分析、原理解刨,总结把这个pdf的脉络理解清楚了,之前内存100%的问题也排查找到了,至于是什么问题,暂且不提(可放在下文的彩蛋之中),因为这个不会影响我们"高性能pdf预览功能"的实现。
废话不多说了,下面就让我们穿越到代码世界吧!滴滴 ~ jun(科技的声音)~
2 实现效果图

在线预览网址:http://116.198.200.217:6005/
git仓库地址:github.com/JACK-ZHANG-...
由上图可见,我们实现pdf的预览功能,同时具有页面跳转、放大缩小(20%-1000%)、缩略图、下载、打印、编辑等功能,可以说是常规的pdf预览及编辑操作都具备了。这就是pdfjs-dist5.2.133版本的pdf预览插件,同时我也对这个源码进行了自定义修改,兼容了大部分的浏览器,增加了下载、打印的权限校验,同时也修改了部分样式,完成了贴合业务场景的高级pdf预览器的实现!
那么这个pdf预览器是如何从0 -> 初步引入项目 -> 兼容大部分现代浏览器 -> 自定义修改源码样式 -> 自定义增加下载、打印权限校验,完成这一系列的操作,打造成为一个完整的贴合业务的pdf预览器的,请看我下面的"实现过程"章节,为屏幕前的有缘的你细细道来~
3 实现过程
3.1 初步引入项目
首先我们要有一个可以正常启动起来的项目,比如一个vue项目,或者我们跟着
官网教程\] [cn.vuejs.org/guide/quick...](https://link.juejin.cn?target=https%3A%2F%2Fcn.vuejs.org%2Fguide%2Fquick-start.html%23creating-a-vue-application%25C2%25A0 "https://cn.vuejs.org/guide/quick-start.html#creating-a-vue-application%C2%A0")
创建一个空的vue项目 `npm create vue@latest` ,又或者直接拿我的项目来用(一步到位:[github.com/JACK-ZHANG-...](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FJACK-ZHANG-coming%2FfrontEnd-all-knowledge%2Ftree%2Fmaster%2Fexamples%2FfunnyDemo%2Fa09vue3%25E9%25AB%2598%25E6%2580%25A7%25E8%2583%25BDpdf%25E9%25A2%2584%25E8%25A7%2588%25E5%2599%25A8 "https://github.com/JACK-ZHANG-coming/frontEnd-all-knowledge/tree/master/examples/funnyDemo/a09vue3%E9%AB%98%E6%80%A7%E8%83%BDpdf%E9%A2%84%E8%A7%88%E5%99%A8"))。
#### 3.1.1 下载pdfjs-dist网页离线包
好的,现在我们都有了一个可以启动起来的项目了,那么下面我们来到pdfjs-dist的官网页面:[mozilla.github.io/pdf.js/gett...](https://link.juejin.cn?target=https%3A%2F%2Fmozilla.github.io%2Fpdf.js%2Fgetting_started%2F%23download "https://mozilla.github.io/pdf.js/getting_started/#download") 。
进来以后我们选择 **Prebuilt (older browsers)** 这个类型版本下载,这种可以适配大部分的新旧浏览器,功能基本也是很齐全的。

我之前下载的是5.2.133,现在看了一下,才过了一两个月,又更新了一个新的版本(当屏幕的你进入这个链接时估计又更新了几个版本吧)。如果想和我下一个版本的可以到官方的github仓库里面下载:
[github.com/mozilla/pdf...](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fmozilla%2Fpdf.js%2Freleases "https://github.com/mozilla/pdf.js/releases")

不过这都没啥问题,小的版本更新不会有多大变化,不影响我们的技术落地。干就完了!
下载完以后,我们把下载好的压缩包解压一下,里面有两个文件夹,放到我们的项目里面,我是放在这里(记住放的位置,后面要根据对应路径引用):

#### 3.1.2 引入项目运行
ok,刚刚我们已经把pdfjs-dist这个网页插件下载下来了并且放入了项目中了,我们需要有一个**空白页面** 用来做pdf展示,我是用的`iframe` 标签来展示的,同时我们还需要有一个可以直接访问**pdf的url链接** (大家可以把pdf上传到图床或者服务器,或者让后端直接提供一个链接,访问能够直接下载pdf,如果没有可以暂且用我的: [http://116.198.200.217:7501/api/v1/user/getPdfFile?file=%E6%B5%8B%E8%AF%95pdf%E6%96%87%E4%BB%B61.pdf](https://link.juejin.cn?target=http%3A%2F%2F116.198.200.217%3A7501%2Fapi%2Fv1%2Fuser%2FgetPdfFile%3Ffile%3D%25E6%25B5%258B%25E8%25AF%2595pdf%25E6%2596%2587%25E4%25BB%25B61.pdf "http://116.198.200.217:7501/api/v1/user/getPdfFile?file=%E6%B5%8B%E8%AF%95pdf%E6%96%87%E4%BB%B61.pdf") ),下面是我pdf展示的关键代码:
`template` 标签里面的关键代码:
```ini
```
`script`标签里面的关键代码:
```ini
currentFileUrl.value = ''
currentFileUrl.value = encodeURIComponent(`http://116.198.200.217:7501/api/v1/user/getPdfFile?file=%E6%B5%8B%E8%AF%95pdf%E6%96%87%E4%BB%B61.pdf`)
```
好了,经过这两段关键代码,我们就能在界面上面看到pdf的效果了,效果如下:

如果不知道咋运行的小伙伴也不要着急,文末有完整代码,如果你能访问github直接访问文章开头的github仓库也能直接下载完整项目代码。
### 3.2 兼容老版本设备
#### 3.2.1 下载老版本的预览器网页
之前我们已经下载了较新版本的pdfjs-dist插件了,但是呢,根据国内硬件的迭代速度,总有一些用户还在使用老版本的设备(比如安卓7啦、低版本的浏览器啦等,当然这里我们是不考虑IE浏览器的\>_\<)。
好滴,现在我们还要再下载一个旧一点的pdfjs-dist插件作为备用,我这里又下载了个旧的版本 `2.0.943` 的这个,下载网址:[github.com/mozilla/pdf...](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fmozilla%2Fpdf.js%2Freleases%3Fpage%3D5 "https://github.com/mozilla/pdf.js/releases?page=5") (这里面有很多个版本,很上面的下载官网是一个地址)

下载完以后,我们可以把压缩好的文件夹放在此处:

由上图可见,我直接创建了个pdf文件夹,然后放在了里面,至于我为啥把旧版本pdf预览器文件夹的名字叫做"pdf"呢,这是因为我之前也是从别的项目里面copy的,图省事(lan)。
#### 3.2.2 进行新老设备判断
直接上关键代码,其原理依旧是通过`iframe`来渲染,然后通过函数判断新旧设备进行展示不同的`iframe`。
`template` 标签里面的关键代码:
```ini
```
`script`标签里面的关键代码:
```typescript
currentFileUrl.value = ''
currentFileUrl.value = encodeURIComponent(
`http://116.198.200.217:7501/api/v1/user/getPdfFile?file=%E6%B5%8B%E8%AF%95pdf%E6%96%87%E4%BB%B61.pdf`,
)
const pdfViewVersionType = ref<'' | 'newest' | 'old'>('')
// 检测设备
const checkDevice = () => {
if (isMobileDevice(false, 'LowerDevice')) {
pdfViewVersionType.value = 'old'
} else {
pdfViewVersionType.value = 'newest'
}
}
const isMobileDevice = (
excludeIpad: boolean = false,
customFlag: 'IOS' | 'ANDROID' | 'PC' | 'LowerDevice' | '' = '',
) => {
// @ts-expect-error 忽略报错
const userAgent = navigator.userAgent || navigator.vendor || window.opera
let flag = false
// 检查常见的移动设备的用户代理
if (/android/i.test(userAgent)) {
flag = true
}
// @ts-expect-error 忽略报错
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
flag = true
}
if (/windows phone/i.test(userAgent)) {
flag = true
}
if (/mobile/i.test(userAgent)) {
flag = true
}
// 排除列表
if (excludeIpad && /iPad|iPod/.test(userAgent)) {
flag = false
}
// 自定义标识 单独校验某种设备,其他都不看
if (customFlag === 'IOS') {
flag = /iPad|iPhone|iPod/.test(userAgent) ? true : false
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 && (flag = true) // iPad伪装成Mac的情况
} else if (customFlag === 'LowerDevice') {
// 目前将iPad|iPhone|iPod 都视为较低的设备
flag = /iPad|iPhone|iPod/.test(userAgent) ? true : false
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1 && (flag = true) // iPad伪装成Mac的情况
}
// 如果都不匹配,则认为是PC端
return flag
}
onMounted(() => {
checkDevice()
})
```
通过如上的判断,我们就能根据IOS、安卓、PC来写显示不同的类型,目前我是将 `iPad|iPhone|iPod` 看作低版本,直接显示低版本的样式(因为用最新的pdf预览器在一些苹果设备上会显示不出来)。
对于低版本设备显示的样式如下:

### 3.3 自定义修改源码样式
当使用官方的pdf预览器时总会有一些样式我(产)们(品)会觉得需要优化一下,这里以`pdfjs-5.2.133-legacy-dist` 这个版本来举例,其实每个版本都大差不差,学会一种其他的也就能自得其然的举一反三了。
比如说我们想要把下图所示弹框中的"pdf生成器"那行文字去掉:

首先我们使用审查元素找到该元素对应的id,然后找到`public\lib\pdfjs-5.2.133-legacy-dist\web\viewer.html` 和 `C:\GitHub\frontEnd-all-knowledge\examples\funnyDemo\a09vue3高性能pdf预览器\public\lib\pdfjs-5.2.133-legacy-dist\web\viewer.mjs` 这两个文件(这两个文件是这个pdf预览器核心代码,一个用于写页面样式,一个是js交互事件),然后将和这个`producerField` dom元素id相关的内容注释或者删掉即可。操作代码截图如下:
`viewer.html`文件:

`viewer.mjs`文件:

如此修改完,界面运行效果如下:

由此可见界面那行文字样式已经被我们去掉了。以此举一反三,我们可以在`viewer.html`文件和`viewer.mjs`文件中添加我们自定义的样式以及事件。
### 3.4 自定义增加下载、打印权限校验
官方提供的pdf预览器网页中为我们提供了下载以及打印的操作,然后在我们实际的项目使用中,下载和打印功能往往是需要加权限控制的。那么下面就将为大家展示一下如何增加权限控制,思路:给pdfjs-dist预览器的源码的下载、打印按钮的元素上增加自定义属性,然后再通过`iframe` 标签去监听带有这个属性的按钮的点击事件,当用户点击相应按钮时捕捉到用户的点击事件,进行检验判断,如果有权限则触发该按钮的点击事件,反之提示无权限,点击事件的触发终止。
关键代码如下:
在`C:\GitHub\frontEnd-all-knowledge\examples\funnyDemo\a09vue3高性能pdf预览器\public\lib\pdfjs-5.2.133-legacy-dist\web\viewer.html` 文件中添加`data-require-perm="true"` 属性

应用层的代码文件 `PdfView.vue` 中的关键代码如下:
```xml