产品需求: pdf是分章节上传到服务器上的, 我拿到的是url数组对象, 所以要实现根据具体页码 请求不同的url地址效
效果图: 支持滚动加载, 使用elementui 的分页实现页码跳转, 支持放大缩小
1.我们使用是 nuxt开发, 首先安装pdfjs-dist
2. plugins中引入
3.组件中引入
import pdfjsLib from '~/plugins/pdf';
4. 核心代码, 后端放回的是一个url数组对象, 需要根据页码计算出请求哪个地址, 在数组中插入的位置
_loadFile(url, index = 0, offsetPage = 0, offsetIndex = 0, needJump = false) {
if (this.isRenderring) {
return;
}
if (index === this.lastIndex) {
return;
};
this.lastIndex = index;
if (this.pdfData.some(item => item.index == index)) {
return;
}
if (index < 0 || !url || offsetPage >= this.totalPage) {
return;
}
// console.log('_loadFile执行===>', { url, index, offsetPage, offsetIndex, needJump });
this.isRenderring = true;
pdfjsLib.getDocument({ url }).promise.then((pdf) => {
this.pdfDoc = pdf;
const pages = pdf.numPages;
// 计算该插入的位置
let idx = 0;
this.pdfData.forEach((it, num) => {
if (it.index < index) {
idx = num + 1; // 插入的是下一个位置
} else {
return;
}
});
// 当前的url 能分几组
const count = Math.ceil(pages / this.size);
const start = 1 + (index - offsetIndex) * this.size + offsetPage;
const end = index - offsetIndex < count - 1 ? (index - offsetIndex + 1) * this.size + offsetPage : pages + offsetPage;
const pageList = this.range(start, end);
// 插入一组连续的页码
this.pdfData.splice(idx, 0, {
url,
index, // 第几组 从零开始
offsetIndex, // 前面的url分了几组
offsetPage, // 前面的url 一共到第几页
idx, // 该分组在数组中的序号
count, // 当前url能分几组
pages, // 当前url的页码数
prev: index - offsetIndex === 0 ? null : index - 1,
next: index - offsetIndex === count - 1 ? null : index + 1,
pageList,
length: pageList.length,
index: index,
start, //
end // 当前分组的页码范围
});
if (this.pdfData.length > 6) {
this.pdfData = this.pdfData.slice(idx < 3 ? 0 : idx - 3, idx + 3);
}
// 等canvas挂载到dom上后,
this.$nextTick(() => {
pageList.forEach(async (it, num) => {
await this._renderPage(it, offsetPage);
if (num === pageList.length - 1) {
this.isRenderring = false;
if (needJump) {
document.getElementById('the-canvas' + this.currentPage).scrollIntoView();
}
}
});
});
});
}
// 渲染指定页到对应的canvas上面
_renderPage(num, offsetPage = 0) {
try {
return this.pdfDoc.getPage(num - offsetPage).then((page) => {
let canvas = document.getElementById(`the-canvas${num}`);
let ctx = canvas.getContext('2d');
let viewport = page.getViewport({ scale: document.querySelector('.canvas-cotainner').offsetWidth / page.getViewport({ scale: 1 }).width });
canvas.width = viewport.width;
canvas.height = viewport.height;
this.pageHeight = parseInt(viewport.height + 10);
let renderContext = {
canvasContext: ctx,
viewport: viewport
};
return page.render(renderContext);
});
} catch (err) {
console.log('_renderPage===>', err);
this.isRenderring = false;
}
},
// 模版
<div
class="canvas-cotainner"
v-scroll="loadMore"
>
<template v-for="item in pdfData">
<canvas
v-for="page in item.pageList"
:data-prev="item.prev"
:data-next="item.next"
:data-index="item.index"
:data-start="item.start"
:data-end="item.end"
:data-page="page"
:id="'the-canvas'+page"
:key="`${page}${resize}`"
></canvas>
</template>
</div>
5.使用自定义指令,在容器盒子添加滚动事件
directives: {
scroll: {
bind(el, binding, vnode) {
let beforeScrollTop = 0;
let timer;
el.addEventListener('scroll', function (e) {
if (timer) {
clearTimeout(timer);
}
if (Math.abs(this.scrollTop - beforeScrollTop) > 300) {
beforeScrollTop = this.scrollTop;
return;
}
timer = setTimeout(() => {
if (vnode.context.isRenderring) {
return;
}
if (this.scrollTop === beforeScrollTop) {
return;
}
// 判断滚动方向
const beforeTopDistance = beforeScrollTop;
const direction = this.scrollTop - beforeScrollTop > 0 ? 'down' : 'up';
beforeScrollTop = this.scrollTop;
// 计算出容器视图内 当前显示的哪个canvas
const idx = Math.floor(this.scrollTop / (vnode.context.pageHeight));
const scrollDistance = this.scrollHeight - this.scrollTop - this.clientHeight;
if (this.children[idx]) {
vnode.context.currentPage = parseInt(this.children[idx].dataset.page);
binding.value(direction, this.children[idx]);
}
}, 20);
}, { capture: false, passive: false, once: false });
}
}
}
// 滚动加载函数
// 每次加载都去加载下一组, 每组为10页,
// 当单前链接url读完的时候, 尝试去计算有没有下一个url
loadMore(dir, dom) {
if (dir === 'down') {
// 当前链接读完
if (dom.dataset.next === undefined) {
// 期望去读下一组的第一页
this.calcUrlByPage(dom.dataset.end - 0 + 1);
if (this.currUrl) {
this.offsetIndex = dom.dataset.index - 0 + 1;
this.offsetPage = dom.dataset.end - 0;
this._loadFile(this.currUrl, dom.dataset.index - 0 + 1, dom.dataset.end - 0, dom.dataset.index - 0 + 1);
}
} else if (!this.isRenderring) {
const i = this.pdfData.findIndex(item => item.index == dom.dataset.index);
if (!this.pdfData[i + 1] || this.pdfData[i + 1].index != dom.dataset.next) {
if (dir !== this.dir) {
this.calcUrlByPage(dom.dataset.end - 0 + 1);
}
if (this.currUrl) {
this._loadFile(this.currUrl, dom.dataset.next - 0, this.offsetPage, this.offsetIndex);
}
}
}
}
// && dom.dataset.page < dom.dataset.start + 3
if (dir === 'up') {
if (dom.dataset.prev === undefined) {
this.calcUrlByPage(dom.dataset.start - 1);
if (this.currUrl) {
this.offsetIndex = dom.dataset.index - 1;
this._loadFile(this.currUrl, dom.dataset.index - 1, this.offsetPage, this.offsetIndex);
}
} else if (!this.isRenderring) {
const i = this.pdfData.findIndex(item => item.index == dom.dataset.index);
if (!this.pdfData[i - 1] || this.pdfData[i - 1].index != dom.dataset.prev) {
if (dir !== this.dir) {
this.calcUrlByPage(dom.dataset.start - 1);
}
if (this.currUrl !== '') {
this._loadFile(this.currUrl, dom.dataset.prev - 0, this.offsetPage, this.offsetIndex);
}
}
}
}
this.dir = dir;
}
### 6.pdfData的数据格式,

### 7. 问题补充
#### 跨域,
跨域需要后端配置 nginx, 在响应头中加'Access-Control-Allow-Origin' '*'
'Access-Control-Allow-Origin' '*'
### pdf.worker.min.js 或者pdf.worker.js 找不到 请求这个资源报404怎么办?
*** 直接把pdf.worker.min.js 复制到静态资源中去引用, 如图

### 8 跳到指定页码
<el-pagination
@size-change="sizeChange"
@current-change="currentChange"
:current-page.sync="currentPage"
:page-size="1"
layout="prev, pager, next, jumper"
:total="totalPage"
class="pagebar"
></el-pagination>
currentChange(val) {
// if (this.isRenderring) {
// return;
// }
this.isRenderring = false;
if (val > 0) {
this.currentPage = val;
}
try {
if (!document.getElementById('the-canvas' + val)) {
this.calcUrlByPage(val);
const index = Math.floor((val - 1 - this.offsetPage) / this.size);
if (this.currUrl) {
this._loadFile(this.currUrl, index + this.offsetIndex, this.offsetPage, this.offsetIndex, true);
}
} else {
document.getElementById('the-canvas' + val).scrollIntoView();
}
} catch (err) {
console.log('currentChange===>', err);
}
},