vue3实现PDF预览

备注:先仅记录pdf预览功能,翻页等功能日后整理。

两种pdf预览方式:

1、vue-pdf-embed 和 vue3-pdfjs 插件

2、pdfjs-dist 插件

一、vue-pdf-embed 和 vue3-pdfjs

1、安装插件

npm i vue-pdf-embed
npm i vue3-pdfjs

2、封装pdf预览组件

PDFView.vue 文件

<template>
  <div
    id="page-view"
    :style="{
      width: '100%',
      height: `100%`
    }"
  >
    <vue-pdf-embed :source="state.source" :page="state.pageNum" textLayer />
  </div>
</template>

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

import VuePdfEmbed from 'vue-pdf-embed';

import { createLoadingTask } from 'vue3-pdfjs';

const props = defineProps({
  pdfUrl: {
    type: String,
    required: true
  }
});

const state = reactive({
  source: props.pdfUrl, //预览pdf文件地址

  pageNum: 1, //当前页面

  numPages: 0 // 总页数
});

onMounted(() => {
  const loadingTask = createLoadingTask(state.source);

  loadingTask.promise.then((pdf) => {
    state.numPages = pdf.numPages;
  });
});
</script>

3、在使用的地方调用组件

<template>
<div class="pdf-container">
    <PDFView :pdfUrl="jsPdf" v-if="jsPdf && jsPdf != ''" />
    <div v-else >加载中...</div>
</div>
</template>

<script setup>
import PDFView from './PDFView.vue';

import jsPdf from 'pdf文件地址';

</script>

如果pdf文件来源于后端返回的二进制流,可做如下处理(举例一种方法):

const jsPdf = ref('')
// ...接口调用,返回的二进制数据 data
const blob = window.URL.createObjectURL(new Blob([data], { type: 'application/pdf' }));
jsPdf.value = blob;

// 界面关闭时需要释放ObjectURL
window.URL.revokeObjectURL(jsPdf.value);
jsPdf.value = '';

备注:pdf预览界面样式自行调整。

二、pdfjs-dist

1、安装插件

npm i pdfjs-dist

2、使用方式一(直接引用)

<template>
<div class="pdf-container">
    <canvas v-for="page in state.pdfPages" :key="page" id="pdfCanvas" />
    <div id="text-view"></div>
</div>
</template>

<script setup>
import 'pdfjs-dist/web/pdf_viewer.css';
import * as PDF from 'pdfjs-dist';
import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs'; //必须

import jsPdf from 'pdf文件地址';

const state = reactive({
  // 总页数
  pdfPages: 1,
  // 页面缩放
  pdfScale: 2
});

let pdfDoc = null;

const loadFile = (url) => {
  PDF.getDocument({
    url,
    cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/',
    cMapPacked: true
  }).promise.then((pdf) => {
    pdfDoc = pdf;
    // 获取pdf文件总页数
    state.pdfPages = pdf.numPages;
    nextTick(() => {
      renderPage(1); // 从第一页开始渲染
    });
  });
};

const renderPage = (num) => {
  pdfDoc.getPage(num).then((page) => {
    const canvas = document.getElementById('pdfCanvas');
    const ctx = canvas.getContext('2d');
    const viewport = page.getViewport({ scale: state.pdfScale });
    canvas.width = viewport.width;
    canvas.height = viewport.height;
    const renderContext = {
      canvasContext: ctx,
      viewport
    };
    page.render(renderContext);
  });
};

</script>

import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs'; 必须要引用,否则渲染时会报错:Error: No “GlobalWorkerOptions.workerSrc“ specified.

如果要一次性展示全部页面的话,可以将代码修改成:

<template>
    <div id="pdf-container">
        <canvas v-for="page in state.pdfPages" :key="page" :id="`page-${page}`" />
        <div id="text-view"></div>
    </div>
</template>

// 修改renderPage
<script setup>
const renderPage = (num)=> {
    pdfDoc.getPage(num).then((page) => {
        // 绑定 id 值对应的元素
        const canvas = document.getElementById(`page-${num}`);
        ...
        page.render(renderContext);
        // state.pdfPages 为 pdf 文件总页数
        if (num < state.pdfPages) {
            renderPage(num + 1);
        }
    });
}
</script>

可能出现的问题:

部分字体出现乱码或浏览器控制台出现警告:

解决:在 getDocument 方法中追加 cMapUrl 和 cMapPacked 参数:

PDF.getDocument({
    url,
    cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/',
    cMapPacked: true,
})

注意:cMapUrl 参数可指定为本地文件路径,可在路径 node_modules/pdfjs-dist/cmaps 中获取。通过测试发现,该警告即便不处理依然不影响页面展示

3、使用方式二(封装组件)

创建 PDFBook.vue 文件

<template>
  <div ref="flipRef" class="flip-book-wrapper">
    <div class="container">
      <div id="flipBook" class="flip-book"></div>
    </div>
  </div>
  <div
    class="pdf-loading"
    v-loading="isLoading"
    element-loading-text="文档加载中..."
    v-if="isLoading"
  ></div>
</template>

<script setup>
import * as pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs';

const props = defineProps({
  url: {
    type: String
  },
  width: {
    type: Number
  },
  height: {
    type: Number
  },
  page: {
    type: Number
  },
  catalog: {
    type: Array
  }
});

const numPages = ref(0);
const scale = ref(1.0);
const pdfInit = async () => {
  const pdfContainer = document.querySelector('.flip-book');
  if (!pdfContainer) {
    return;
  }
  const loadingTask = pdfjs.getDocument({
    url: props.url
  });
  const pdf = await loadingTask.promise;
  numPages.value = pdf.numPages;

  for (let index = 0; index < numPages.value; index++) {
    const page = await pdf.getPage(index + 1);
    const viewport = page.getViewport({ scale: scale.value });

    const canvas = document.createElement('canvas');

    const PIXEL_RATIO = 3;
    canvas.width = viewport.width * PIXEL_RATIO; // 实际渲染像素
    canvas.height = viewport.height * PIXEL_RATIO; // 实际渲染像素
    const context = canvas.getContext('2d');
    if (!context) {
      return;
    }
    const renderContext = {
      canvasContext: context,
      viewport: viewport,
      transform: [
        PIXEL_RATIO / window.devicePixelRatio,
        0,
        0,
        PIXEL_RATIO / window.devicePixelRatio,
        0,
        0
      ]
    };
    await page.render(renderContext).promise;
    pdfContainer.appendChild(canvas);
  }
};

const isLoading = ref(false);
const init = async () => {
  isLoading.value = true;
  await pdfInit();
  isLoading.value = false;
};
onBeforeUnmount(() => {});
onMounted(() => {
  init();
});
</script>

调用组件

<template>
    <PDFBook ref="pdfEBook" :url="jsPdf" v-if="jsPdf"></PDFBook>
</template>

<script setup>
import PDFBook from './PDFBook.vue';

import jsPdf from 'pdf文件地址';
</script>

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值