vue3实现高性能pdf预览器功能可行性方案及实践(pdfjs-dist5.x插件使用及自定义修改)

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)** 这个类型版本下载,这种可以适配大部分的新旧浏览器,功能基本也是很齐全的。 ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/7a6ff62b3b4202d97985d4b2f18d050f.webp) 我之前下载的是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") ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/0bfb9e3cc114a832497ce8cd4b6be526.webp) 不过这都没啥问题,小的版本更新不会有多大变化,不影响我们的技术落地。干就完了! 下载完以后,我们把下载好的压缩包解压一下,里面有两个文件夹,放到我们的项目里面,我是放在这里(记住放的位置,后面要根据对应路径引用): ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/dad73fc5ff093e8e5bfd6e667eb83809.webp) #### 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的效果了,效果如下: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/19168d38a3a8e23f7c05184e48a8c3d2.webp) 如果不知道咋运行的小伙伴也不要着急,文末有完整代码,如果你能访问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") (这里面有很多个版本,很上面的下载官网是一个地址) ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/243123d18c3b4ddeb115d3b94cf7333f.webp) 下载完以后,我们可以把压缩好的文件夹放在此处: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/ebd52fb1515d0fbc222fb3716a34c72d.webp) 由上图可见,我直接创建了个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预览器在一些苹果设备上会显示不出来)。 对于低版本设备显示的样式如下: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/b909768a2256bb50d4ca4b1e1959dfce.webp) ### 3.3 自定义修改源码样式 当使用官方的pdf预览器时总会有一些样式我(产)们(品)会觉得需要优化一下,这里以`pdfjs-5.2.133-legacy-dist` 这个版本来举例,其实每个版本都大差不差,学会一种其他的也就能自得其然的举一反三了。 比如说我们想要把下图所示弹框中的"pdf生成器"那行文字去掉: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/f33dad431376a7ff89d171435230af31.webp) 首先我们使用审查元素找到该元素对应的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`文件: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/9971658aa848882021d8655a7443d931.webp) `viewer.mjs`文件: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/c2635043ce65859e60d23a4fc9c40a35.webp) 如此修改完,界面运行效果如下: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/0c6715ad2a031346af82e482001e93db.webp) 由此可见界面那行文字样式已经被我们去掉了。以此举一反三,我们可以在`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"` 属性 ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/55388831e90df3f1587d88a54dcb8fda.webp) 应用层的代码文件 `PdfView.vue` 中的关键代码如下: ```xml ``` 下图所示为打印按钮没有权限的效果: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/b6b21798930dd6dd7b1953074f5fe71e.webp) 如此,下载及打印的权限校验功能已经实现。 ## 4 项目线上部署bug解决 当我们把pdf预览器开发好以后,肯定是要部署到生产环境(线上/服务器)给别人使用的,但是呢,如果你是首次部署到服务器上面,大概率会出现如下这种情况: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/cb61c84533a81193a43eb6fe9940fb37.webp) 我们看到控制台分别报了`Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.` 和 `Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.` 这两个错误,这个问题是我们的服务器对 一些`MIME` 类型文件不支持的原因,此刻我们修改一些服务器的配置就可以解决。你的服务器可能是IIS或者Nginx或者是其他,我这里就以宝塔为例(我的是宝塔),修改一下宝塔该网站的nginx相关的配置,在nginx加入如下配置: ```ini location ~* .(?:js|mjs)$ { default_type application/javascript; expires 12h; add_header Cache-Control "public"; } ``` ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/a7af72200669010e57306eb779746303.webp) 加完这个nginx配置然后保存,重启服务器,pdf就可以正常预览了,效果如下: ![image.png](https://oss.xyyzone.com/jishuzhan/article/1947542332471029762/1a7807b040c4f3dcbf62859455100103.webp) ## 5 完整代码 上面我们将pdf预览器的完整实现过程做了一个讲解,并且增加了相应自定义操作,为了防止有小伙伴觉得代码比较零散,下面粘贴出完整代码,或者也可以直接访问我的github仓库([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"))获取。 完整代码如下: ```xml ​ ``` ## 6 总结 这个高性能pdf预览器是基于vue3做的,但是本质上还是使用的pdfjs-dist这个插件,也就是说原生的html、js与react理论上也是都可以实现的,只要我们安装pdfjs-dist这个插件就可以了。 当然,实现高性能的pdf预览器也还有很多种方案,可以选用别的比较流行的插件,或者使用pdfjs-dist内置的方法`getDocument` ,读取相应的pdf文件流然后手动渲染到`canvas`标签,最一开始笔者就是采用的这种方案(然后嘞,就出现了大文件渲染卡顿的问题,为什么嘞,因为我在放大pdf的时候把canvas也给放大,当文件过大canvas的尺寸都能达到上万像素以上,严重消耗电脑的性能,卡顿感可想而知。\>_\< 嘿嘿,恭喜你获取开头前言所说的彩蛋内容\~ 后续,但是笔者当时排查到这个问题时候依然还是想 采用原来的读取pdf文件流手动渲染canvas 的方案去解决嘛,然后也尝试了限制设备像素比、分块渲染等方案,但是依然还是存在bug------分块渲染显示不全、放大缩小有错位显示不全现象、滚动有问题等,还学艺不精、学艺不精呀。 故直接采用了pdfjs-dist官方封装的预览器,拿来就能用,真香! 然后再改改源码,增加一些业务上自定义的功能就可以了`>_<` 真是选对方案简单翻倍啊\~)。 由于水平有限,文章难免有所纰漏之处,欢迎大家批评指正,也欢迎大家分享自己的对搭建高性能pdf预览的见解及实践经验 \~ 互相进步 \~

相关推荐
凌辰揽月7 分钟前
贴吧项目总结二
java·前端·css·css3·web
代码的余温8 分钟前
优化 CSS 性能
前端·css
在雨季等你22 分钟前
奋斗在创业路上的老开发
android·前端·后端
yume_sibai29 分钟前
Vue 生命周期
前端·javascript·vue.js
阿廖沙10241 小时前
前端不改后端、不开 Node,彻底搞定 Canvas 跨域下载 —— wsrv.nl 野路子实战指南
前端
讨厌吃蛋黄酥1 小时前
🌟 React Router Dom 终极指南:二级路由与 Outlet 的魔法之旅
前端·javascript
花颜yyds1 小时前
three.js学习
前端·three.js
SixHateSeven1 小时前
🚀 TSX动态编译的黑科技,快如闪电!
前端·编译器
aiwery1 小时前
前端国际化技术实践
前端
兵临天下api1 小时前
电商数据分析实战:利用 API 构建商品价格监控系统
前端