前端接入 pdfjs-dist 渲染 pdf 文件踩坑
由于最终渲染页面属于自己定制的,所以这边通过直接引用 pdfjs-dist
下的插件,只处理canvas 渲染部分,其他功能在此基础上自行定制,下面遇到的问题也是在使用插件进行 canvas 渲染时出现的问题
1、工程接入 pdfjs 时在安卓60版本下渲染失败,并报错
由于 pdfjs-dist 中语法偏高,如含有 ||= 等高级语法,所以在使用时,需要额外给pdfjs-dist进行 babel 再编译。
在webpack 中的rule下新增以下部分,需要注意使用"@babel/preset-env"
时需要设置 modules
为 commonjs
,否则默认 auto
的情况编译出的代码是无法直接使用的
{
test: /.m?js$/,
include: [
path.resolve("node_modules/pdfjs-dist/"),
],
use: {
loader: "babel-loader",
options: {
presets: [
[
require("@babel/preset-env"),
{
modules: "commonjs"
}
]
],
plugins: ["@babel/plugin-transform-runtime"]
}
}
},
2、node环境下部分机型使用 pdfjs 时无法渲染,如鸿蒙 p30
验证时发现使用时提示
structuredClone is not defined
发现 structuredClone
的兼容性更差,不知道为什么上面配置的 babel 降级怎么没有将这个语法给降了。
查看 github 官方 issue ,发现确实有这个问题,在使用时使用 pdfjs-dist/legacy/build
下面对应的文件即可解决,官方文档中也有对应的说明。
然后我思考了一下,发现这个目录下是解决兼容问题,那么 问题1 是不是也能通过这个问题解决,然后验证了一下,发现真不能,有些语法仍需要做 babel 处理,如 Private class fields
等高级语法仍在该版本插件下使用
3、node环境下使用 pdf.worker.js,无报错,也无渲染
由于在 node 环境下通过将 pdf.worker.js
设置为 pdfjs
的 GlobalWorkerOptions.workerSrc
无法生效,所以这里需要使用 pdf.worker.entry.js
作为 workerSrc
,对应 issue
此处注意使用的是 legacy
下的还是 build
下的需要与 pdfjs
引用的保持一致
4、体积问题
直接使用插件时体积较大,考虑到使用min版本时,pdf.work.entry
没有对应的min版本,查看源码如下:
(typeof window !== "undefined"
? window
: {}
).pdfjsWorker = require("./pdf.worker.js");
那么我使用时是否直接按照上述方式调整,来降低 worker 的对应的体积呢,现象是能,整理数据发现,体积的增量几乎就在 worker 的体积上体现了
const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.min.js");
pdfjsLib.GlobalWorkerOptions.workerSrc = (typeof window !== "undefined"
? window
: {}
).pdfjsWorker = require("pdfjs-dist/legacy/build/pdf.worker.min.js");
原工程 | 1119k | ||
---|---|---|---|
接入build版本 | 3538k | +2419k | |
接入 legacy 版本 | 4035k | +3916k | |
worker min 版本 | worker 非 min 版本 | ||
接入build min 版本 | 2450k +1331k | 3657k +2538k | |
接入 legacy min 版本 | 2793k +1674k | 3735k +3616k |
根据工程的兼容性和体积需求,目前考虑使用 接入 legacy 的 min 版本且 worker min 版本
5、跨域问题
当 pdfjs 加载远程pdf时,不免会出现 CORS 跨域的问题,3种解决方案,主要都是通过下载文件后再访问来解决
1、将 pdf 下载到本地,放到工程中进行本地引用,此方式不仅增加工程体积,还不灵活
2、将 pdf 下载到静态资源服务器上,在工程中访问静态资源服务器上的资源进行实现,此方式需要手动操作,比较低效
3、通过调用服务器接口由服务器进行资源下载,待下载完成后由服务器提供对应的资源的 ArrayBuffer
进行访问,此方式比较灵活
根据我们工程的现状, 选择了第三种方式
附录
其实好像大部分问题都能在官方说明里找到,只不过遇到的问题可能在文档里没有说那么细,但仔细查找下 issue 也是能找到对应的解决方案。
下面简单写个使用的例子吧
const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.min.js");
pdfjsLib.GlobalWorkerOptions.workerSrc = (typeof window !== "undefined"
? window
: {}
).pdfjsWorker = require("pdfjs-dist/legacy/build/pdf.worker.min.js");
function init(data) {
// data可以为url,也可以为服务端返回的 arrayBuffer 或服务端返回的 base64,前端转的 arrayBuffer
try {
const loadingTask = pdfjsLib.getDocument(data);
this.pdf = await loadingTask.promise;
this.pageNum = await this.pdf.numPages || 0;
console.log('pageNum', this.pageNum)
this.initCanvas();
this.renderPdf(1);
} catch (err) {
this.onError({
message: "pdf 文件加载失败",
code: 1
})
}
}
function renderPdf(num = 1) {
this.pdf.getPage(num).then((page) => {
// 设置canvas相关的属性
const canvas = document.getElementById("pdfCanvas_" + num);
const ctx = canvas.getContext("2d", {
alpha: false
});
const dpr = window.devicePixelRatio || 1;
const bsr =
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1;
const ratio = dpr / bsr;
const viewport = page.getViewport({ scale: this.pdfScale * ratio }); // 设置缩放比率
const viewRatio = viewport.height / viewport.width;
canvas.width = viewport.width * ratio;
canvas.height = viewport.height * ratio;
canvas.style.width = (this.container.clientWidth - 20) * this.pdfScale + "px";
canvas.style.height = (this.container.clientWidth - 20) * viewRatio * this.pdfScale + "px";
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
// 数据渲染到canvas画布上
page.render(renderContext);
if (this.pageNum > num) {
setTimeout(() => {
return this.renderPdf(num + 1);
});
}
});
}
转载自:https://juejin.cn/post/7221072002335916069