前端PDF渲染,pdf.js的多种渲染使用方式

10,993 阅读2分钟

前言

在上次的项目中有渲染pdf文档的需求,在Chrome浏览器中是可以直接渲染pdf文档的,而项目跑在webview中,经过测试不支持直接渲染pdf,那么就需要用第三方库了,然后我用的是pdf.js这个库

使用方式

pdf.js有多种使用模式

  1. 直接使用官方的示例iframe方式
  2. 直接引入pdf.js使用方式
  3. 通过npm引入的方式

而通过npm引入方式,它又分两种渲染模式,一种是canvas渲染,另外一种是HTML渲染(这种方式渲染出来的文本内容是可以复制的)

我这里将分别讲解iframe模式和npm引入模式,和canvas渲染 和 HTML渲染

文末将提供源码示例

官方完整iframe使用

首先下载官方稳定包,官方下载地址:Getting Started (mozilla.github.io)

image.png

使用

下载解压后,通过vscode打开,启动服务模式,访问网址http://127.0.0.1:5500/web/viewer.html 我们可以在链接后面添加 file=pdf.js文件地址 参数来实现渲染我们想要的pdf

http://127.0.0.1:5500/web/viewer.html?file=http://www.xxx.cn/test.pdf

问题:

如果你的文件链接不同域名,会产生跨域问题,我们需要通过修改viewer.js文件来解决

打开该js文件,搜索 file origin does not match viewer 注释这行代码即可,如图

image.png

在vue项目中使用

首先将解压后的文件放到 public文件夹内,这里为了减少体积,我将一些无用的map文件删除了

image.png

封装一个组件,接收一个url参数,之后哪里需要哪里调用该组件渲染即可

<template>
  <iframe :src="src" frameborder="0" width="100%" height="100%" class="pdf-iframe"></iframe>
</template>
<script setup>
import { computed } from "vue"
const props = defineProps({
  url: {
    type: String,
    required: true
  }
})
const emit = defineEmits(["pdfload"])
const src = computed(() => {
    // 拼接url路径,如果你的url有参数,那么需要URL编码一下
  return `/web-pdfjs2.6.347/web/viewer.html?file=${props.url}`
})
</script>

调用

<template>
  <div class="iframebox">
    <PdfIframe :url="url"/>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import PdfIframe from './pdf-iframe/index.vue'
const url = ref('http://www.xxx.com/test2.pdf')  // pdf文件的路径或者url
</script>
<style>
.iframebox {
  height: 100vh;
}
</style>

效果

image.png

npm方式使用

pdfjs的npm包名称为:pdfjs-dist,我这里使用的是指定的版本,不同版本用法有差异 这种方式相当于自定义渲染,也就是像翻页,打印,等都需要自己实现

canvas方式渲染

安装指定版本

npm install pdfjs-dist@2.16.105

封装组件

<template>
  <div ref="pdfRef" class="pdf-view"></div>
</template>
<script setup>
import { ref, watch } from "vue"
import PdfjsWorker from "pdfjs-dist/build/pdf.worker.js?worker"
const props = defineProps({
  url: { type: String, required: true}
})
const pdfRef = ref(null)
// 侦听props的url改变,则立即切换渲染pdf
watch(
  () => props.url,
  () => {
    setTimeout(() => init(), 1)
  },
  { immediate: true }
)

// 渲染pdf
async function init() {
   // 异步加载pdf.js
  const PDFJS = await import("pdfjs-dist/build/pdf.js")
  if (typeof window !== "undefined" && "Worker" in window) {
    PDFJS.GlobalWorkerOptions.workerPort = new PdfjsWorker()
  }
  // 加载文档
  let loadingTask = PDFJS.getDocument({ url: props.url })
  loadingTask.__PDFDocumentLoadingTask = true
  const pdf = await loadingTask.promise  // 使用await等待加载完毕
  // 循环渲染每一页
  for (let i = 1; i <= pdf.numPages; i++) {
    const page = await pdf.getPage(i)
    let pixelRatio = 3
    let viewport = page.getViewport({ scale: 1 })
    let divPage = window.document.createElement("div") // canvas的外层div
    // 使用canvas渲染
    let canvas = divPage.appendChild(window.document.createElement("canvas"))
    divPage.className = "page"
    pdfRef.value.appendChild(divPage)
    canvas.width = viewport.width * pixelRatio  // 计算宽度
    canvas.height = viewport.height * pixelRatio
    let renderContext = {
      canvasContext: canvas.getContext("2d"),
      viewport: viewport,
      transform: [pixelRatio, 0, 0, pixelRatio, 0, 0]
    }
    await page.render(renderContext).promise // 一页一页的渲染
    divPage.className = "page complete"
  }
  console.log('pdf页面全部渲染完毕', pdf.numPages, pdf)
}
</script>
<style scoped lang="scss">
.pdf-view {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}
// 组件样式
:deep() {
  .page {
    position: relative;
    canvas {
      width: 100%;
    }
  }
}
</style>

调用

<template>
  <PdfCanvas :url="url"/>
</template>
<script setup>
import { ref } from 'vue';
import PdfCanvas from './pdf-canvas/index.vue'
const url = ref('/test2.pdf')  // pdf的链接
</script>

渲染效果

image.png

dom方式渲染

安装指定版本

npm install pdfjs-dist@2.0.943

话不多说,上代码,封装成组件

<template>
  <div id="pageContainer">
    <div id="viewer" class="pdfViewer"></div>
  </div>
</template>
<script setup>
import { defineProps, defineEmits, onMounted } from 'vue'
import pdfjsLib from 'pdfjs-dist/build/pdf.js'
import PdfjsWorker from "pdfjs-dist/build/pdf.worker.min.js?worker"
import { PDFViewer } from 'pdfjs-dist/web/pdf_viewer'
import 'pdfjs-dist/web/pdf_viewer.css'
pdfjsLib.GlobalWorkerOptions.workerPort = new PdfjsWorker()

const props = defineProps({
  url: {
    type: String,
    default: '',
  },
})
const emit = defineEmits(['pdfload'])
onMounted(() => getPdf()) // 等组件初始化完成后在渲染pdf,因为要挂载到div上
async function getPdf() {
  const container = document.getElementById('pageContainer') // 获取挂载点
  // 实例化pdf视图
  const pdfViewer = new PDFViewer({
    container: container,
  })
  // 加载pdf文件
  const loadingTask = pdfjsLib.getDocument(props.url)
  // 使用await等待加载完毕
  const pdf = await loadingTask.promise
  // 开始绘制到dom
  pdfViewer.setDocument(pdf)
  // 监听pagerendered来实现 判断 是否渲染完成,如果要打印一定要渲染完成后再打印,不然会有空白
  document.addEventListener('pagerendered', function (e) {
    if (e.detail.pageNumber === pdf.numPages) {
      emit('pdfload', pdf) // 渲染完成,通知父组件
    }
  })
}
</script>

调用

<template>
  <div>
    <PdfViewer :url="url"/>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import PdfViewer from './pdf-viewer/index.vue'
const url = ref('/test2.pdf')  // pdf链接
</script>

渲染效果

会比canvas方式要 清晰一点,因为是dom渲染

image.png

最后

本文中所有调用方式的示例项目代码:

gitee:gitee.com/xlybyte/vue…

github:github.com/xlybyte/vue…


pdfjs的一些常用属性和方法,记录一下

属性/方法描述
PDFJSPDFJS 全局对象
PDFJS.versionpdf.js 版本
PDFJS.workerSrcworker 路径
PDFJS.getDocument(source)获取 PDF 文档
PDFJS.getDocument(params)获取 PDF 文档,params 中包含 URL 和需要的其他信息
PDFJS.disableWorker禁用 worker
PDFJS.disableStream禁用流模式
PDFJS.disableAutoFetch禁用自动获取
PDFJS.getDocumentLoadingTask(source)获取异步加载 PDF 文档的任务
PDFJS.getDocumentLoadingTask(params)获取异步加载 PDF 文档的任务,params 中包含 URL 和需要的其他信息
PDFJS.getDocumentProxy(params)获取代理对象
PDFJS.TextRenderingMode文本渲染模式
PDFJS.createPromiseCapability()创建 promise 能力对象