一、先直接看看效果吧
放大后
缩小后
也可以分页显示
二、选用vue-pdf-embed和vue3-pdfjs的原因
选用这两个的插件是因为如果实现pdf预览其实使用iframe标签就可以的,但是使用iframe标签实现的比较臭,vue-pdf-embed是能够自定义样式的,更加灵活,我也是第一次尝试这个,也遇到了一些坑,后面会跟大家分享,反正按照这个来就能实现,源码我也放后面了。
三、注意点及踩坑
1、我这里项目搭建是用的Vite也不知道使用vue cli会不会有什么问题
2、vue-pdf-embed我开始是pnpm i vue-pdf-embed 安装了最新的,后面会发现pdf预览不出来,找了原因是版本的问题,我就pnpm i vue-pdf-embed@1.2.1 切换到1.2.1版本就好了
3、vue3-pdfjs在这是用来做监听pdf页数的当然还有别的用处。
4、当预览成果后会发现文字并未出来,f12会报这个
warning: error during font loading: the cmap "baseurl" parameter must be specified, ensure that the "cmapurl" and "cmappacked" api parameters are provided.
原因就是缺少中文包,加上下面这段就行了,这段代码在源码里面
5、还有个问题是scale缩放开始无效,原因大概是因为在使用 vue-pdf-embed
组件的 scale
参数时发现无法实现动态更改,且 width
参数无法设置成百分比的形式,所以采用修改父容器的方案实现。
四、直接上源码了
//父组件
<template><div class="main"><div class="home-box"><pdfView :pdfUrl="jsPdf"/></div></div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import pdfView from '@/components/pdfView/index.vue';
import jsPdf from './jsPdf.pdf';
</script>
<style lang="scss" scoped>
.main {width: 100vw;height: 100vh;display: flex;align-items: center;justify-content: center;.home-box{width: 80%;}
}
</style>
子组件
<template><div class="pdf-preview"><divclass="pdf-wrap":style="{ transform: `translate(-50%,-50%) scale(${scaleData})` }"><vue-pdf-embed:source="state.source":style="state.scale"class="vue-pdf-embed":page="state.pageNum"/></div><div class="page-tool"><div class="page-tool-item" @click="lastPage">上一页</div><div class="page-tool-item" @click="nextPage">下一页</div><div class="page-tool-item">{{ state.pageNum }}/{{ state.numPages }}</div><div class="page-tool-item" @click="zoomIn">放大</div><div class="page-tool-item" @click="zoomOut">缩小</div><div class="page-tool-item" @click="pageRest">重置缩放</div></div></div>
</template>
<script setup lang="ts">
import { reactive, onMounted, computed, 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: {url: props.pdfUrl,cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.9.359/cmaps/',cMapPacked: true}, //预览pdf文件地址pageNum: 1, //当前页面scale: 1, // 缩放比例numPages: 0 // 总页数
})
const scaleData = ref(1.0)
const pageHeight = ref('100%')
//上一页
function lastPage() {if (state.pageNum > 1) {state.pageNum -= 1}
}
//下一页
function nextPage() {if (state.pageNum < state.numPages) {state.pageNum += 1}
}// 放大
function zoomIn() {scaleData.value += 0.1pageHeight.value = parseInt(pageHeight.value) - 5.0 + '%'
}
// 缩小
function zoomOut() {scaleData.value -= 0.1pageHeight.value = parseInt(pageHeight.value) + 5.0 + '%'
}
// 页面 放大/缩小 还原默认值
function pageRest() {scaleData.value = 1.0pageHeight.value = '100%'
}onMounted(() => {// debugger// console.log(props.pdfUrl)// debugger//获取pdf总页数const loadingTask = createLoadingTask(state.source)loadingTask.promise.then((pdf: { numPages: number }) => {// debuggerstate.numPages = pdf.numPages// debugger})
})
</script>
<style lang="scss" scoped>
.pdf-preview {position: relative;height: 100vh;padding: 20px 0;box-sizing: border-box;background-color: e9e9e9;.pdf-wrap {position: absolute;width: 100%;top: 50%;left: 50%;height: 100%;overflow-y: auto;.vue-pdf-embed {height: 100%;text-align: center;width: 80%;border: 1px solid #e5e5e5;margin: 0 auto;box-sizing: border-box;}}.page-tool {position: absolute;bottom: 35px;padding-left: 15px;padding-right: 15px;display: flex;align-items: center;background: rgb(66, 66, 66);color: white;border-radius: 19px;z-index: 100;cursor: pointer;margin-left: 50%;transform: translateX(-50%);.page-tool-item {padding: 8px 15px;padding-left: 10px;cursor: pointer;}}
}
</style>