前言:
        最近和小伙伴们一起合作完成一个企业级知识库项目,其中一个功能就是后端把所有格式的文件转换为PDF,前端实现渲染PDF文件从而实现预览,干了整整一周(考虑到低版本浏览器的兼容),试用了几种方案(iframe预览已被废弃,不适用本项目,想了解的同学自行搜索),终于实现了这个功能。

方案:Vue3+pdfjs-dist+canvas 

1.pdfjs-dist展示PDF文件的原理解释

pdfjs-dist展示pdf文档的原理,实际上是将pdf中的内容渲染到解析,然后渲染到 canvas 中进行展示,因此我们使用pdfjs渲染出来的pdf文件,实际上是一张张canvas图片。

2.安装pdfjs-dist

pdfjs-dist下载官网

建议安装: "pdfjs-dist": "^2.12.313"

npm i pdfjs-dist@2.12.313

打开VScode,使用ctl+`打开控制台,输入:npm i pdfjs-dist 安装pdfjs-dist 切记要指定版本

3. 搭建基础的页面代码

话不多说,上代码:

<template>
  <div class="pdf-preview-box">
    <!-- <div class="pdf_down">
          <div class="pdf_set_left" @click="scaleD()">放大</div>
          <div class="pdf_set_middle" @click="scaleX()">缩小</div>
          <div class="pdf-pre" @click="prePage">上一页</div>
          <div class="pdf-next" @click="nextPage">下一页</div>
      </div> -->
    <canvas class="pdf-viewer"
        v-for="i in pdfParams.pdfPageTotal"
        :key="i"
        :id="'pdf-render' + i">
    </canvas>
  </div>
</template>
4.使用Vue3语法实现PDF文件的多页展示
4.1引入ref
import { onMounted, ref, reactive } from 'vue'
4.2将会用到的数据声明为响应式
const props = defineProps({
  pdfUrl: {
    type: String,
    default: '/testPdf.pdf',
    required: true,
  },
  containerWidth: {
    type: Number,
    default: 700,
    required: true,
  },
});

const pdfParams = reactive({
  currentPageNumber: 1,
  pdfScale: 1.0,
  pdfPageTotal: 0, // 总页数
});

// 不要定义为ref或reactive格式,就定义为普通的变量
let pdfDoc = null;
4.3定义获取pdf文档流与pdf文件的页数的方法:loadFile
// 这里必须使用异步去引用pdf文件,直接去import会报错,也不知道为什么
const loadFile = async () => {
  let pdfjs = await import('pdfjs-dist/build/pdf')
  let pdfjsWorker = await import('pdfjs-dist/build/pdf.worker.entry')
  pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker
  // 此文件位于public/testPdf.pdf
  pdfjs.getDocument(props.pdfUrl).promise.then(async doc => {
    pdfDoc = doc
    pdfParams.pdfPageTotal = doc.numPages
    // // 仅加载第一页  注释  取消页码切换
    // await getPdfPage(pdfParams.currentPageNumber)
    // 加载pdf所有页
    for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {
      await getPdfPage(pageNum)
    }
  })
}
4.4定义渲染pdf文件的方法:renderPage
// 加载pdf的某一页
const getPdfPage = (number) => {
  return new Promise((resolve, reject) => {
    pdfDoc.getPage(number).then(page => {
      const canvas = document.getElementById(`pdf-render${number}`)
      const context = canvas.getContext('2d')
      const scale = 1; // 缩放比例
      const dpr = window.devicePixelRatio || 1;
      const bsr =
        context.webkitBackingStorePixelRatio ||
        context.mozBackingStorePixelRatio ||
        context.msBackingStorePixelRatio ||
        context.oBackingStorePixelRatio ||
        context.backingStorePixelRatio ||
        1;
      const ratio = dpr / bsr;
      const viewport = page.getViewport({ scale: pdfParams.pdfScale }); // 获取窗口大小
      const canvasWidth = Math.floor(viewport.width * ratio);
      const canvasHeight = Math.floor(viewport.height * ratio);
      // const canvasWidth = props.containerWidth;
      // const canvasHeight = Math.floor(viewport.height * ratio) * (props.containerWidth / Math.floor(viewport.width * ratio));
      canvas.width = canvasWidth;
      canvas.height = canvasHeight;
      // canvas.style.width = Math.floor(viewport.width) + 'px'
      // canvas.style.height = Math.floor(viewport.height) + 'px'
      canvas.style.width = Math.floor(props.containerWidth) + 'px'
      canvas.style.height = Math.floor(viewport.height * props.containerWidth / viewport.width) + 'px'

      let renderContext = {
        canvasContext: context,
        viewport: viewport,
        // 这里transform的六个参数,使用的是transform中的Matrix(矩阵)
        // transform: [1, 0, 0, -1, 0, viewport.height]
        transform: [ratio, 0, 0, ratio, 0, 0]
      }

      // 进行渲染
      page.render(renderContext).promise.then(() => {
        resolve();
      }).catch(error => {
        reject(error);
      });
    }).catch(error => {
      reject(error);
    });
  });
}
4.5在onMounted钩子中调用loadFile方法
//调用loadFile方法
onMounted(async () => {
  await loadFile()
})
5完整代码实现:
<!--
 * @Author: 码农键盘上的梦
 * @Description:  pdf预览插件    "pdfjs-dist": "^2.12.313",  指定版本 配合 canvas 实现一个组件
 * 
-->
<template>
  <div class="pdf-preview-box">
    <!-- <div class="pdf_down">
          <div class="pdf_set_left" @click="scaleD()">➕</div>
          <div class="pdf_set_middle" @click="scaleX()">➖</div>
          <div class="pdf-pre" @click="prePage">上一页</div>
          <div class="pdf-next" @click="nextPage">下一页</div>
      </div> -->
    <canvas class="pdf-viewer" v-for="i in pdfParams.pdfPageTotal" :key="i" :id="'pdf-render' + i"></canvas>
  </div>
</template>

<script setup>
import { onMounted, ref, reactive } from 'vue'

const props = defineProps({
  pdfUrl: {
    type: String,
    default: '/testPdf.pdf',
    required: true,
  },
  containerWidth: {
    type: Number,
    default: 700,
    required: true,
  },
});

const pdfParams = reactive({
  currentPageNumber: 1,
  pdfScale: 1.0,
  pdfPageTotal: 0, // 总页数
});

// 不要定义为ref或reactive格式,就定义为普通的变量
let pdfDoc = null;

onMounted(async () => {
  await loadFile()
})

// 这里必须使用异步去引用pdf文件,直接去import会报错,也不知道为什么
const loadFile = async () => {
  let pdfjs = await import('pdfjs-dist/build/pdf')
  let pdfjsWorker = await import('pdfjs-dist/build/pdf.worker.entry')
  pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker
  // 此文件位于public/testPdf.pdf
  pdfjs.getDocument(props.pdfUrl).promise.then(async doc => {
    pdfDoc = doc
    pdfParams.pdfPageTotal = doc.numPages
    // // 仅加载第一页  注释  取消页码切换
    // await getPdfPage(pdfParams.currentPageNumber)
    // 加载pdf所有页
    for (let pageNum = 1; pageNum <= doc.numPages; pageNum++) {
      await getPdfPage(pageNum)
    }
  })
}

// 加载pdf的某一页
const getPdfPage = (number) => {
  return new Promise((resolve, reject) => {
    pdfDoc.getPage(number).then(page => {
      const canvas = document.getElementById(`pdf-render${number}`)
      const context = canvas.getContext('2d')
      const scale = 1; // 缩放比例
      const dpr = window.devicePixelRatio || 1;
      const bsr =
        context.webkitBackingStorePixelRatio ||
        context.mozBackingStorePixelRatio ||
        context.msBackingStorePixelRatio ||
        context.oBackingStorePixelRatio ||
        context.backingStorePixelRatio ||
        1;
      const ratio = dpr / bsr;
      const viewport = page.getViewport({ scale: pdfParams.pdfScale }); // 获取窗口大小
      const canvasWidth = Math.floor(viewport.width * ratio);
      const canvasHeight = Math.floor(viewport.height * ratio);
      // const canvasWidth = props.containerWidth;
      // const canvasHeight = Math.floor(viewport.height * ratio) * (props.containerWidth / Math.floor(viewport.width * ratio));
      canvas.width = canvasWidth;
      canvas.height = canvasHeight;
      // canvas.style.width = Math.floor(viewport.width) + 'px'
      // canvas.style.height = Math.floor(viewport.height) + 'px'
      canvas.style.width = Math.floor(props.containerWidth) + 'px'
      canvas.style.height = Math.floor(viewport.height * props.containerWidth / viewport.width) + 'px'

      let renderContext = {
        canvasContext: context,
        viewport: viewport,
        // 这里transform的六个参数,使用的是transform中的Matrix(矩阵)
        // transform: [1, 0, 0, -1, 0, viewport.height]
        transform: [ratio, 0, 0, ratio, 0, 0]
      }

      // 进行渲染
      page.render(renderContext).promise.then(() => {
        resolve();
      }).catch(error => {
        reject(error);
      });
    }).catch(error => {
      reject(error);
    });
  });
}


// // 下一页功能
// const prevPage = () => {
//     if (pdfParams.currentPageNumber > 1) {
//         pdfParams.currentPageNumber -= 1
//     } else {
//         pdfParams.currentPageNumber = 1
//     }
//     getPdfPage(pdfParams.currentPageNumber)
// }
// // 上一页功能
// const nextPage = () => {
//     if (pdfParams.currentPageNumber < pdfParams.pdfPageTotal) {
//         pdfParams.currentPageNumber += 1
//     } else {
//         pdfParams.currentPageNumber = pdfParams.pdfPageTotal
//     }
//     getPdfPage(pdfParams.currentPageNumber)
// }

// //放大
// const scaleD = async () => {
//     let max = 0;
//     if (window.screen.width > 1440) {
//         max = 1.4;
//     } else {
//         max = 1.2;
//     }
//     if (pdfParams.pdfScale >= max) {
//         return;
//     }
//     pdfParams.pdfScale = pdfParams.pdfScale + 0.1;
//     await loadFile();
// }
// //缩小
// const scaleX = async () => {
//     let min = 1.0;
//     if (pdfParams.pdfScale <= min) {
//         return;
//     }
//     pdfParams.pdfScale = pdfParams.pdfScale - 0.1;
//     await loadFile();
// }

</script>

<style scoped lang="scss" >
.pdf-preview-box {
  width: 100%;
  position: relative;

  // .pdf_down {
  //     position: fixed;
  //     display: flex;
  //     z-index: 20;
  //     right: 26px;
  //     bottom: 7%;
  //     cursor: pointer;

  //     .pdf_set_left {
  //         width: 30px;
  //         height: 40px;
  //         color: #408fff;
  //         font-size: 15px;
  //         padding-top: 25px;
  //         text-align: center;
  //         margin-right: 5px;
  //         cursor: pointer;
  //     }

  //     .pdf_set_middle {
  //         width: 30px;
  //         height: 40px;
  //         color: #408fff;
  //         font-size: 15px;
  //         padding-top: 25px;
  //         text-align: center;
  //         margin-right: 5px;
  //         cursor: pointer;
  //     }

  //     .pdf-pre {
  //         position: fixed;
  //         display: flex;
  //         z-index: 20;
  //         right: 160px;
  //         bottom: 9%;
  //         cursor: pointer;
  //     }

  //     .pdf-next {
  //         position: fixed;
  //         display: flex;
  //         z-index: 20;
  //         right: 100px;
  //         bottom: 9%;
  //     }
  // }
}
</style>

以上就是实现PDF文件多页展示的内容了,如果其他的小伙伴有其他的方法或者思考请批评指正 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐