vue3中实现pdf翻页效果

278 阅读3分钟

所用插件

pdfjs-dist,turn.js,jquery

由于turnjs需要用到jQuery,所以需要安装并配置jQuery,先配置jQuery,在vue.config.js文件中注入jQuery

image.png

    config.plugin("provide").use(webpack.ProvidePlugin, [{
      $: "jquery",
      jquery: "jquery",
      jQuery: "jquery",
      "window.jQuery": "jquery",
    },]);

引入文件

主要需要这几个文件 image.png

import $ from 'jquery';
import '@/utils/turnjs4/lib/turn.js';
import * as pdfjs from 'pdfjs-dist';
pdfjs.GlobalWorkerOptions.workerSrc = `/pdf.worker.js`;

插件注意 turnjs自己去下载一个,放在utils文件夹里就行,然后就是pdf.worker.js这个文件,之前我在网上找的都是使用下面这种引入pdfjsWorker,但是我自己用着好像有点问题,我这里是自己下载的js文件放在public文件夹下面

import * as pdfjs from 'pdfjs-dist'
// 网上方法 不管用
import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry' 

pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker

个人下载的文件 image.png

逻辑代码实现

里面有些关于pdf大小的是因为我这里后台返回大pdf大小是不固定的所以需要进行调整

const getData = async () => {
  try {
    const result = await getPdf({ id: route.query.id });
    await pdfInit(result);
    await onTurn(); // 确保在 PDF 数据加载完成后执行翻页初始化
  } catch (error) {
    isSuccess.value = false;
    console.error('请求出错', error);
  }
};
onMounted(async () => {
  await getData();
});
const pdfInit = async (url: string) => {
  loading.value = true; // 设置加载状态为 true,显示加载动画
  const pdfContainer = document.querySelector('#flipbook'); // 获取填充 PDF 的容器
  if (!pdfContainer) {
    return;
  }
  try {
    // 使用 PDF.js 加载 PDF 文件
    const loadingTask = pdfjs.getDocument({ url }); // 创建加载任务
    const pdf = await loadingTask.promise; // 等待 PDF 加载完成
    const container = document.querySelector('#flipbook'); // 获取 PDF 容器

    // 获取第一页的宽高信息,用于调整显示比例
    const demo = await pdf.getPage(1); // 获取第一页
    viewSize.width = demo._pageInfo?.view[2]; // 获取第一页的宽度
    viewSize.height = demo._pageInfo?.view[3]; // 获取第一页的高度

    // 遍历 PDF 所有页面并渲染
    for (let index = 0; index < pdf.numPages; index++) {
      const page = await pdf.getPage(index + 1); // 获取当前页面
      let viewport = null; // 定义视图端口

      // 根据 PDF 页面的宽高比调整显示比例
      if (viewSize.width < viewSize.height) {
        if (page._pageInfo.view[2] > page._pageInfo.view[3]) {
          viewport = page.getViewport({ scale: 0.8 }); // 纵向页面,横向较长,缩小显示
        } else {
          viewport = page.getViewport({ scale: 1 }); // 纵向页面,正常比例显示
        }
      } else {
        viewport = page.getViewport({ scale: 1 }); // 横向页面,正常比例显示
      }

      // 创建 Canvas 元素用于渲染 PDF 页面
      const canvas = document.createElement('canvas');
      canvas.height = 1123; // 设置 Canvas 高度
      canvas.width = viewport.width; // 设置 Canvas 宽度

      const context = canvas.getContext('2d'); // 获取绘图上下文
      if (!context) {
        throw new Error('Cannot get canvas context'); // 如果无法获取上下文,抛出错误
      }

      // 定义渲染上下文
      const renderContext = {
        canvasContext: context,
        viewport: viewport,
      };

      // 渲染 PDF 页面到 Canvas 上
      await page.render(renderContext).promise;

      // 将渲染好的 Canvas 包装到 DOM 结构中
      const divPage = document.createElement('div'); // 页面容器
      divPage.classList.add('page'); // 添加样式

      const divPageContent = document.createElement('div'); // 页面内容容器
      divPageContent.classList.add('page-content'); // 添加样式

      canvas.className = 'canvas'; // 设置 Canvas 样式
      divPageContent.appendChild(canvas); // 将 Canvas 添加到内容容器
      divPage.appendChild(divPageContent); // 将内容容器添加到页面容器
      container.appendChild(divPage); // 将页面容器添加到 PDF 容器
    }
  } catch (error) {
    console.error('Error rendering PDF:', error); // 捕获并打印错误
  } finally {
    loading.value = false; // 加载完成,隐藏加载动画
  }
};
const onTurn = () => {
  $('#flipbook').turn({ // 初始化 Turn.js 翻页动画
    autoCenter: true, // 自动居中
    height: viewSize.height, // 设置高度
    width: viewSize.height > viewSize.width ? viewSize.width * 2 : viewSize.width, // 设置宽度
    display: viewSize.height > viewSize.width ? 'double' : 'single', // 单页或双页显示
    elevation: 50, // 设置翻页的三维效果
    duration: 500, // 翻页速度
    gradients: true, // 翻页时的阴影渐变
    acceleration: true, // 硬件加速
    page: 1, // 初始显示页面
    pages: pageCav.value.length, // 总页数
    turnCorners: 'bl,br,tl,tr,l,r', // 设置可翻页的页角
    when: { // 监听事件
      turning: async function (e, page, view) { // 翻页中
        console.log('e', e);
        console.log('page', page);
        console.log('view', view);
      },
      turned: function (e, page) { // 翻页完成
        currentPage.value = page; // 更新当前页
      },
      last: function (e, page) { // 最后一页
        message.info('最后一页');
      },
    },
  });
};

完整代码

<template>
  <div class="back" @click="back">
    <a href="javascript:;">
      <LeftOutlined style="font-size: 18px;" />
    </a>
    <input ref="selectFile" type="file" style="display: none;" />
  </div>
  <div v-if="isSuccess" class="box" v-show="!loading">
    <div id="flipbook"></div>
    <div class="prv">
      <LeftOutlined @click="prev" class="change" />
      <!-- <a-button type="primary" @click="prev">上一页</a-button> -->
    </div>
    <div class="next">
      <RightOutlined @click="next" class="change" />
      <!-- <a-button type="primary" @click="next">下一页</a-button> -->
    </div>
  </div>
  <a-row justify="center" :gutter="40" style="margin-top: 30px" v-if="!loading">
    <a-col v-if="!isSuccess">
      <a-result status="404" title="" sub-title="页面加载失败">
        <template #extra>
          <a-button type="primary" @click="back">返回上一页</a-button>
        </template>
      </a-result>
    </a-col>
  </a-row>
  <div class="example" v-show="loading">
    <a-spin style="margin-top: 20%;" tip="数据加载中......." />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, reactive, watch } from 'vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import { useRouter, useRoute } from 'vue-router';
import { getPdf } from '@/api/dzgb/gbzs';
import { message } from 'ant-design-vue';
import $ from 'jquery';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import '@/utils/turnjs4/lib/turn.js';
import * as pdfjs from 'pdfjs-dist';
pdfjs.GlobalWorkerOptions.workerSrc = `/pdf.worker.js`;
// 判断请求是否成功
const isSuccess = ref(true);
const router = useRouter();
const loading = ref(true);
const route = useRoute();
const selectFile = ref(null);
// 存储pdf宽高
const viewSize = reactive({
  width: null,
  height: null,
});
// 页数
const pageCav = ref<any>([]);
const currentPage = ref(1);
const back = () => {
  router.back();
};
const pdfInit = async (url) => {
  loading.value = true;
  const pdfContainer = document.querySelector('#flipbook');
  if (!pdfContainer) {
    return;
  }
  try {
    // 使用 PDF.js 加载 PDF 数据
    const loadingTask = pdfjs.getDocument({ url: `${url}` });
    const pdf = await loadingTask.promise;
    const container = document.querySelector('#flipbook');
    // 获取pdf原件大小
    const demo = await pdf.getPage(1);
    viewSize.width = demo._pageInfo?.view[2];
    viewSize.height = demo._pageInfo?.view[3];
    for (let index = 0; index < pdf.numPages; index++) {
      const page = await pdf.getPage(index + 1);
      let viewport = null;
      if (viewSize.width < viewSize.height) {
        if (page._pageInfo.view[2] > page._pageInfo.view[3]) {
          viewport = page.getViewport({ scale: 0.8 });
        } else {
          viewport = page.getViewport({ scale: 1 });
        }
      } else {
        viewport = page.getViewport({ scale: 1 });
      }
      const canvas = document.createElement('canvas');
      canvas.height = 1123;
      canvas.width = viewport.width;
      const context = canvas.getContext('2d');
      if (!context) {
        throw new Error('Cannot get canvas context');
      }
      const renderContext = {
        canvasContext: context,
        viewport: viewport,
      };
      await page.render(renderContext).promise;
      const divPage = document.createElement('div');
      divPage.classList.add('page');
      const divPageContent = document.createElement('div');
      divPageContent.classList.add('page-content');
      canvas.className = 'canvas';
      divPageContent.appendChild(canvas);
      divPage.appendChild(divPageContent);
      container.appendChild(divPage);
    }
  } catch (error) {
    console.error('Error rendering PDF:', error);
  } finally {
    loading.value = false;
  }
};


const getData = async () => {
  try {
    const result = await getPdf({ id: route.query.id });
    await pdfInit(result);
    await onTurn(); // 确保在 PDF 数据加载完成后执行翻页初始化
  } catch (error) {
    isSuccess.value = false;
    console.error('请求出错', error);
  }
};
onMounted(async () => {
  await getData();
});
watch(() => route.query.id, () => {
  console.log('111');
});
const onTurn = () => {
  $('#flipbook').turn({
    autoCenter: true, //自动居中, 默认false
    height: viewSize.height, //高度
    width: viewSize.height > viewSize.width ? viewSize.width * 2 : viewSize.width, //宽度
    display: viewSize.height > viewSize.width ? 'double' : 'single', //单页显示/双页显示  single/double
    elevation: 50,
    duration: 500, //翻页速度(毫秒), 默认600ms
    gradients: true, //翻页时的阴影渐变, 默认true
    acceleration: true, //硬件加速, 默认true, 如果是触摸设备设置为true
    page: 1, //设置当前显示第几页
    pages: pageCav.value.length, //总页数
    turnCorners: 'bl,br,tl,tr,l,r', // 设置可翻页的页角(都试过了,乱写 4个角都能出发卷起), bl,br   tl,tr   bl,tr
    when: {
      //监听事件
      turning: async function (e, page, view) {
        console.log('e', e);
        console.log('page', page);
        console.log('view', view);
      },
      turned: function (e, page) {
        currentPage.value = page;
        // 翻页后触发
      },
      last: function (e, page) {
        message.info('最后一页');
      },
    },
  });
};
// 翻页
const prev = () => {
  $('#flipbook').turn('previous');
};
const next = () => {
  $('#flipbook').turn('next');
};
</script>

<style scoped lang="less">
.box {
  // width: 952px;
  width: 100%;
  overflow: hidden;
  margin: 0 auto;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;

  .prv {
    position: absolute;
    top: 40%;
    left: 0;
  }

  .next {
    position: absolute;
    top: 40%;
    right: 0;
  }
}

.page {
  margin: 0 auto;
  /* 居中对齐 */
}

.example {
  text-align: center;
  background: rgba(0, 0, 0, 0.05);
  border-radius: 4px;
  margin-bottom: 20px;
  padding: 30px 50px;
  height: 100%;
  align-items: center;
  justify-content: center;
  margin: 20px 0;
}

.change {
  font-size: 32px;
}

.back {
  height: 30px;
  width: 30px;
  // background: red;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  float: left;
  // position: fixed;
}
</style>