使用pdfjs-dist实现查看pdf

520 阅读2分钟

当前基于vite 5.3.1vue 3.4.29pdfjs-dist 5.3.31

实现效果

效果一:不分页,下滑查看完整pdf

image.png

效果二: 分页,点击切换页面

image.png

具体实现

使用npm引入pdfjs-dist

npm install pdfjs-dist

代码实现

<script setup>
import {onBeforeRouteUpdate, useRoute} from 'vue-router'
//获取 pdf 地址
import {getWikiPageDetail} from "@/api/modules/api.wiki.page.js";
import { onMounted, reactive } from 'vue';
//在引入的时候需要考虑打包方式是vite
import * as pdfjs from 'pdfjs-dist';
pdfjs.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs",import.meta.url).toString();
const pdfParams = reactive({
    showPage: 0, //是否分页显示  1 是 0 否
    pageNumber: 1, // 当前页
    total: 0, // 总页数
    scale: 1, // 缩放
    rotate: 0 // 旋转角度
});

// 不要定义为ref或reactive格式,就定义为普通的变量
let pdfDoc = null;
let currentRoute = useRoute();
onBeforeRouteUpdate((to) => {
  //点击route更新的时候加载pdf
  loadPageDetail(to.params.pageId)
});
onMounted(async() => {
  //初始进入加载pdf
  loadPageDetail(currentRoute.params.pageId)
});

const loadPageDetail = async (pageId) =>{
  // 加载pdf文件,本地文件会有跨域问题,下面这个地址是不对的,自行更换
    const result =  (await getWikiPageDetail(pageId)).data
    const url = result.pageContent.content;

    pdfjs.getDocument(url).promise.then(doc => {
      pdfDoc = doc;
      pdfParams.total = doc.numPages;
      getPdfPage(1);
    });
}

// 加载pdf的某一页
const getPdfPage = (number) => {
    if(pdfParams.total < number){
        return
    }
    pdfDoc.getPage(number).then((page) => {
        // 获取视图,并设置缩放
        const viewport = page.getViewport(
            {
                scale: pdfParams.scale, // 缩放
                rotation: pdfParams.rotate // 旋转
            });
        const outputScale = window.devicePixelRatio || 1;
        // 获取canvas
        //分页返回: 'pdf-render'  不分页返回: 'pdf-render-num'
        const canvas = document.getElementById('pdf-render' + ((pdfParams.showPage == 1)?'':'-'+number));
        const context = canvas.getContext('2d');
        // 设置canvas的宽高
        canvas.width = Math.floor(viewport.width * outputScale);
        canvas.height = Math.floor(viewport.height * outputScale);
        canvas.style.width = Math.floor(viewport.width) + 'px';
        canvas.style.height = Math.floor(viewport.height) + 'px';

        var transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;

        // 渲染pdf
        var renderContext = {
            canvasContext: context,
            transform: transform,
            viewport: viewport
        };
        page.render(renderContext);
        //不分页的情况下,自动加载下一页
        if(pdfParams.showPage == 0){
            getPdfPage(number + 1)
        }
    });
};
// 前一页
const prevPage = () => {
    if (pdfParams.pageNumber > 1) {
        pdfParams.pageNumber -= 1;
    } else {
        pdfParams.pageNumber = 1;
    }
    // 重新渲染
    getPdfPage(pdfParams.pageNumber);
};
// 下一页
const nextPage = () => {
    if (pdfParams.pageNumber < pdfParams.total) {
        pdfParams.pageNumber += 1;
    } else {
        pdfParams.pageNumber = pdfParams.total;
    }
    // 重新渲染
    getPdfPage(pdfParams.pageNumber);
};

// 旋转
const rotatePage = () => {
    pdfParams.rotate += 90;
    // 重新渲染
    getPdfPage(pdfParams.pageNumber);
};
// 放大
const toBig = () => {
    if (pdfParams.scale < 5) {
        pdfParams.scale += 0.5;
    } else {
        pdfParams.scale = 5;
    }
    // 重新渲染
    getPdfPage(pdfParams.pageNumber);
};
// 缩小
const toSmall = () => {
    if (pdfParams.scale > 1) {
        pdfParams.scale -= 0.5;
    } else {
        pdfParams.scale = 1;
    }
    // 重新渲染
    getPdfPage(pdfParams.pageNumber);
};
</script>

<template>
  <div class="main-container">
  <div class="pdf-show-vue">
        <div class="tool-bar">
            <div v-if="pdfParams.showPage == 1">{{ pdfParams.pageNumber }} / {{ pdfParams.total }}</div>
            <div v-else>{{ pdfParams.total }}</div>
            <div>
              <el-button v-if="pdfParams.showPage == 1" type="primary" :disabled="pdfParams.pageNumber == pdfParams.total" @click="nextPage">下一页
              </el-button>
              <el-button v-if="pdfParams.showPage == 1" type="primary" :disabled="pdfParams.pageNumber == 1" @click="prevPage">上一页</el-button>
              <el-button type="primary" @click="rotatePage">旋转</el-button>
              <el-button type="primary" :disabled="pdfParams.scale==5" @click="toBig">放大</el-button>
              <el-button type="primary" :disabled="pdfParams.scale==1" @click="toSmall">缩小</el-button>
            </div>
        </div>
        <!-- 不分页 -->
        <div v-if="pdfParams.showPage == 0">
            <canvas v-for="page in pdfParams.total" :id="'pdf-render-'+page" :key="page" ></canvas>
        </div>
        <!-- 分页 -->
        <div v-else>
            <canvas id="pdf-render"></canvas>
        </div>
  </div>
</div>
</template>

<style  lang="scss" scoped>
.main-container {
  display: flex;
  flex-direction: column;
  transition: margin-left 0.28s;
}
.pdf-show-vue {
  position: relative;
  overflow: hidden;
  box-sizing: border-box;
  height: calc(100vh - $navbar-height);
  overflow-y: scroll; /* 确保y轴有滚动 */
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* Internet Explorer 10+ */
  padding-top: 10px;
  .tool-bar{
    display: flex;
    justify-content: space-between;
    margin-inline: 20px;
    position: sticky;
    top: 0;
    transition: width .28s;
  }
}
canvas{
    display: block; /* 一个canvas占一行 */
    margin: 0 auto; /* 居中 */
}
</style>

代码说明一:引入pdfjs-dist

  • 使用npm install pdfjs-dist引入pdfjs-dist
  • 加载pdf.worker.min.mjs因为是需要String的类型,所以写法和官网上的案例不太一致
import * as pdfjs from 'pdfjs-dist';
pdfjs.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs",import.meta.url).toString();

代码说明二:tool-bar的css样式

  • 悬浮,不因滑动隐藏
position: sticky;
top: 0;
transition: width .28s;
  • 两个子div平均分布
display: flex;
justify-content: space-between;

部署后,使用nginx代理报错

报错内容:Expected a JavaScript 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.

处理

server {
   include mime.types;
   types {
       application/javascript mjs js;
       application/pdf pdf;
   }
}