pdf.js实现web h5预览pdf文件(兼容低版本浏览器)
注意
使用的是pdf.js 版本为 v2.16.105。因为新版本 兼容性不太好,部分手机预览不了,所以采用v2版本。
相关依赖
"canvas": "^2.11.2",
"pdfjs-dist": "^2.16.105",
"core-js-pure": "^3.37.1",
"hammerjs": "^2.0.8", //这个是写手势 双指缩放的 不需要可以去掉
解决部分浏览器或者手机系统的兼容问题
//解决 structuredClone// https://developer.mozilla.org/en-US/docs/Web/API/structuredClone#browser_compatibility// https://gitcode.com/zloirock/core-js/overview?utm_source=csdn_github_acceleratorimport structuredClone from 'core-js-pure/actual/structured-clone';// 解决 TypeError: key.split(...).at is not a function// https://github.com/wojtekmaj/react-pdf/issues/1465import 'core-js/features/array/at';window.structuredClone = structuredClone;
代码
以下为在uniapp vue3 实现 h5 预览pdf文件的代码 有使用vant(手指缩放功能只写了一点,是不能用的)。
<template><div id="pdf-view" ref="pdfView"><!-- <canvas v-for="page in state.pdfPages" :key="page" id="pdfCanvas" />--><div ref="pdfViewContainer"><divv-for="pageNumber in state.pdfPages"v-show="state.pdfPageList.includes(pageNumber)":key="pageNumber":ref="(el) => (pageRefs[pageNumber - 1] = el)"></div></div><je-loading v-show="loading" /></div>
</template>
<script setup>//解决 structuredClone// https://developer.mozilla.org/en-US/docs/Web/API/structuredClone#browser_compatibility// https://gitcode.com/zloirock/core-js/overview?utm_source=csdn_github_acceleratorimport structuredClone from 'core-js-pure/actual/structured-clone';// 解决 TypeError: key.split(...).at is not a function// https://github.com/wojtekmaj/react-pdf/issues/1465import 'core-js/features/array/at';import * as pdfjsWorker from 'pdfjs-dist/lib/pdf.worker.js';// 解决 pdfjsWorker 未定义window.pdfjsWorker = pdfjsWorker;window.structuredClone = structuredClone;// if (!Array.prototype.at) {// Array.prototype.at = function (index) {// if (index < 0) {// index = this.length + index;// }// if (index >= 0 && index < this.length) {// return this[index];// }// return undefined;// };// }import Hammer from 'hammerjs';import * as pdfjsWorker from 'pdfjs-dist/lib/pdf.worker.js';// 解决 pdfjsWorker 未定义window.pdfjsWorker = pdfjsWorker;import 'pdfjs-dist/web/pdf_viewer.css';import * as PDF from 'pdfjs-dist';// import * as PDF from 'pdfjs-dist/build/pdf.js';import { useRoute } from 'vue-router';import { ref, reactive, onMounted, nextTick, defineProps } from 'vue';import { showFailToast } from 'vant';const route = useRoute();const props = defineProps({src: {type: String,default: '',},});const pdfViewContainer = ref(null);const pdfView = ref(null);const pageRefs = ref([]);const loading = ref(false);const state = reactive({// 总页数pdfPages: 1,pdfPageList: [], //有效页码列表// 页面缩放pdfScale: 1,});let pdfDoc = null;async function loadFile(url) {// {// url,// cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/',// cMapPacked: true,// }loading.value = true;// 设置配置选项 手势缩放PDF?.DefaultViewerConfig?.set({handToolOnDblClick: true,mouseWheelScale: true,});let arrayBufferPDF;//// if (navigator.userAgent.indexOf('QQ')) {// const pdfData = await fetch(url);// arrayBufferPDF = await pdfData.arrayBuffer();// }// 解决部分机型浏览器 undefined is not an object(evaluating 'response.body.getReader')// https://www.qingcong.tech/technology/javascript/a-pdfjs-bug-in-qq.html#%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95fetch(url).then(async (pdfData) => {console.log('pdfData', pdfData);if (!pdfData.ok) {loading.value = false;showFailToast({message: '预览地址不存在或已失效',duration: 0,});// window.JE.alert('预览地址不存在', 'error');return;}arrayBufferPDF = await pdfData.arrayBuffer();const loadingTask = arrayBufferPDF? PDF.getDocument({ data: arrayBufferPDF }): PDF.getDocument(url);loadingTask.promise.then((pdf) => {pdfDoc = pdf;// 获取pdf文件总页数state.pdfPages = pdf.numPages;nextTick(() => {for (let i = 0; i < state.pdfPages; i++) {renderPage(i + 1); // 从第一页开始渲染}});});});}function initPinchZoom() {const pdfViewEl = pdfView.value;const hammer = new Hammer(pdfViewEl);// 启用捏合缩放手势hammer.get('pinch').set({ enable: true });// 启用拖动手势,设置拖动方向为所有方向,阈值为0hammer.get('pan').set({ direction: Hammer.DIRECTION_ALL, threshold: 0 });let initialScale = 1; // 初始缩放比例let deltaX = 0; // 当前水平拖动距离let deltaY = 0; // 当前垂直拖动距离let startX = 0; // 拖动开始时的水平位置let startY = 0; // 拖动开始时的垂直位置const MIN_SCALE = 1; // 最小缩放比例const MAX_SCALE = 4; // 最大缩放比例let lastPinchTime = 0; // 上一次捏合事件的时间戳let lastPanTime = 0; // 上一次拖动事件的时间戳// 捏合开始事件处理函数hammer.on('pinchstart', (event) => {initialScale = state.pdfScale; // 记录初始缩放比例startX = deltaX; // 记录拖动开始时的水平位置startY = deltaY; // 记录拖动开始时的垂直位置});// 捏合移动事件处理函数hammer.on('pinchmove', (event) => {const currentTime = Date.now();// 节流控制,限制事件触发频率if (currentTime - lastPinchTime > 50) {event.preventDefault();const scale = event.scale; // 获取当前捏合的缩放比例const newScale = Math.min(Math.max(initialScale * scale, MIN_SCALE), MAX_SCALE); // 计算新的缩放比例,限制在最小和最大缩放比例之间state.pdfScale = newScale; // 更新缩放比例状态applyTransform(); // 应用变换lastPinchTime = currentTime; // 更新上一次捏合事件的时间戳}});// 捏合结束事件处理函数hammer.on('pinchend', (event) => {initialScale = state.pdfScale; // 更新初始缩放比例为当前缩放比例limitPanPosition(); // 限制拖动位置范围renderPages(); // 重新渲染页面});// 拖动开始事件处理函数hammer.on('panstart', (event) => {pdfViewEl.style.transition = 'none'; // 禁用拖动过渡效果startX = deltaX; // 记录拖动开始时的水平位置startY = deltaY; // 记录拖动开始时的垂直位置});// 拖动移动事件处理函数hammer.on('panmove', (event) => {const currentTime = Date.now();// 节流控制,限制事件触发频率if (currentTime - lastPanTime > 50) {const dx = event.deltaX; // 获取当前拖动的水平距离const dy = event.deltaY; // 获取当前拖动的垂直距离deltaX = startX + dx; // 计算新的水平拖动距离deltaY = startY + dy; // 计算新的垂直拖动距离applyTransform(); // 应用变换lastPanTime = currentTime; // 更新上一次拖动事件的时间戳}});// 拖动结束事件处理函数hammer.on('panend', (event) => {pdfViewEl.style.transition = 'transform 0.3s ease'; // 启用拖动过渡效果limitPanPosition(); // 限制拖动位置范围});// 限制拖动位置范围的函数function limitPanPosition() {const pdfWidth = pdfViewEl.clientWidth * state.pdfScale; // 计算PDF页面的实际宽度const containerWidth = pdfViewContainer.value.clientWidth; // 获取容器的宽度const containerHeight = pdfViewContainer.value.clientHeight; // 获取容器的高度// 计算单个页面的平均高度const averagePageHeight =pageRefs.value.reduce((totalHeight, pageRef) => {return totalHeight + (pageRef ? pageRef.clientHeight : 0);}, 0) / state.pdfPageList.length;// 估算总高度,使用PDF文档的总页数乘以单个页面的平均高度const estimatedTotalHeight = state.pdfPages * averagePageHeight * state.pdfScale;// 限制水平拖动距离,确保PDF页面在容器内部deltaX = Math.min(0, Math.max(deltaX, containerWidth - pdfWidth));// 限制垂直拖动距离,确保PDF页面在容器内部,使用估算的总高度deltaY = Math.min(0, Math.max(deltaY, containerHeight - estimatedTotalHeight));applyTransform(); // 应用变换}// 应用变换的函数function applyTransform() {pdfViewEl.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${state.pdfScale})`; // 设置PDF页面的变换样式}}function renderPages() {state.pdfPageList = [];for (let i = 0; i < state.pdfPages; i++) {renderPage(i + 1);}}function renderPage(num) {pdfDoc.getPage(num).then((page) => {// 获取当前页面对应的DOM容器元素const container = pageRefs.value[num - 1];// 创建一个新的canvas元素const canvas = document.createElement('canvas');// 获取canvas的2D渲染上下文const ctx = canvas.getContext('2d');// 获取设备像素比let devicePixelRatio = window.devicePixelRatio || 1;// 获取画布的backing store ratiolet backingStoreRatio =ctx.webkitBackingStorePixelRatio ||ctx.mozBackingStorePixelRatio ||ctx.msBackingStorePixelRatio ||ctx.oBackingStorePixelRatio ||ctx.backingStorePixelRatio ||1;// 获取pdfViewContainer元素的宽度const pdfWrapperElWidth =pdfViewContainer.value.clientWidth ||pdfViewContainer.value.offsetWidth ||pdfViewContainer.value.style.width;// 获取PDF页面的初始视口,缩放比例为1const intialisedViewport = page.getViewport({ scale: 1 });// 计算缩放比例,使PDF页面宽度与容器宽度一致const scale = pdfWrapperElWidth / intialisedViewport.width;// 计算设备像素比与backing store ratio的比值let ratio = devicePixelRatio / backingStoreRatio;// 根据缩放比例获取PDF页面的视口const viewport = page.getViewport({ scale });// 设置canvas的宽度为容器宽度乘以ratio,确保高分辨率下的清晰度canvas.width = pdfWrapperElWidth * ratio;// 设置canvas的高度为视口高度乘以ratio,确保高分辨率下的清晰度canvas.height = viewport.height * ratio;// 设置canvas的样式宽度为100%,与容器宽度一致canvas.style.width = '100%';// 设置canvas的样式高度为auto,根据宽度自适应canvas.style.height = 'auto';// 缩放画布的渲染上下文,根据ratio进行缩放,确保在高分辨率下绘制的清晰度ctx.scale(ratio, ratio);const renderContext = {canvasContext: ctx,viewport,};// 设置页面容器的高度为视口高度container.style.height = `${viewport.height}px`;page.render(renderContext).promise.then(() => {state.pdfPageList.push(num);// 如果 container 存在 canvas元素 覆盖canvas元素container?.firstChild && container.removeChild(container.firstChild);container && container.appendChild(canvas);}).finally(() => {if (num === state.pdfPages) {loading.value = false;}});});}onMounted(() => {const file = route.query.file && JSON.parse(decodeURIComponent(route.query.file));const { relName, previewUrl } = file || {};if (relName) {// 设置 uniapp 当前页面标题uni.setNavigationBarTitle({title: relName,});}if (previewUrl) {loadFile(previewUrl);// nextTick(() => {// initPinchZoom();// });} else {showFailToast({message: '预览地址不存在',duration: 0,});}});
</script>
<style scoped lang="less">uni-page-body {overflow-y: scroll;}
</style>相关文章:
pdf.js实现web h5预览pdf文件(兼容低版本浏览器)
注意 使用的是pdf.js 版本为 v2.16.105。因为新版本 兼容性不太好,部分手机预览不了,所以采用v2版本。 相关依赖 "canvas": "^2.11.2", "pdfjs-dist": "^2.16.105", "core-js-pure": "^3.37.…...
SSID简介
一、 SSID 概念定义 SSID(Service Set Identifier)即服务集标识符。它是无线网络中的一个重要标识,用于区分不同的无线网络。 相当于无线网络的名称,用于区分不同的无线网络。用户在众多可用网络中识别和选择特定网络的依据。通…...
PS通过GTX实现SFP网络通信1
将 PS ENET1 的 GMII 接口和 MDIO 接口 通过 EMIO 方 式引出。在 PL 端将引出的 GMII 接口和 MDIO 接口与 IP 核 1G/2.5G Ethernet PCS/PMA or SGMII 连接, 1G/2.5G Ethernet PCS/PMA or SGMII 通过高速串行收发器 GTX 与 MIZ7035/7100 开发…...
前端面试项目细节重难点(已工作|做分享)(九)
面试官:请你讲讲你在工作中如何开发一个新需求,你的整个开发过程是什么样的? 答:仔细想想,我开发新需求的过程如下: (1)第一步:理解需求文档: 首先&#x…...
区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测
区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现BP-ABKDE的BP神经网络自适应带…...
抢占人工智能行业红利,前阿里巴巴产品专家带你15天入门AI产品经理
前言 当互联网行业巨头纷纷布局人工智能,国家将人工智能上升为国家战略,藤校核心课程涉足人工智能…人工智能领域蕴含着巨大潜力,早已成为业内共识。 面对极大的行业空缺,不少人都希望能抢占行业红利期,进入AI领域。…...
MEMS:Lecture 16 Gyros
陀螺仪原理 A classic spinning gyroscope measures the rotation rate by utilizing the conservation of angular momentum. 经典旋转陀螺仪通过利用角动量守恒来测量旋转速率。 Coriolis Effect and Coriolis Force 科里奥利效应是一种出现在旋转参考系中的现象。它描述了…...
Java中List流式转换为Map的终极指南
哈喽,大家好,我是木头左! 在Java编程中,经常需要将一个List对象转换为另一个Map对象。这可能是因为需要根据List中的元素的某些属性来创建一个新的键值对集合。在本文中,我将向您展示如何使用Java 中的流式API轻松地实…...
【秋招突围】2024届秋招笔试-小红书笔试题-第一套-三语言题解(Java/Cpp/Python)
🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系计划跟新各公司春秋招的笔试题 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📧 清隆这边…...
HAL库开发--STM32的HAL环境搭建
知不足而奋进 望远山而前行 目录 文章目录 前言 下载 安装 解压 安装 添加开发包 修改仓库路径 下载软件开发包(慢,不推荐) 解压已有软件开发包(快,推荐) 总结 前言 在嵌入式系统开发中&#x…...
【DPDK学习路径】七、创建RX/TX队列
上一节我们讲述了如何申请内存池缓冲区以便接下来创建 RX 队列,这一节我们将给出具体如何创建 RX/TX 队列。 在 DPDK 中提供了 rte_eth_rx_queue_setup 及 rte_eth_tx_queue_setup 这两个接口用于接收/发送队列的创建。 下面给出一个为各个网卡创建RX/TX 队列的实例…...
【ArcGISProSDK】OpenItemDialog打开文件对话框
打开单个文件 效果 代码 public async void OpenFunction() {// 获取默认数据库var gdbPath Project.Current.DefaultGeodatabasePath;OpenItemDialog openItemDialog new OpenItemDialog() { Title "打开要素文件",InitialLocation gdbPath,Filter ItemFilte…...
TensorFlow2.x基础与mnist手写数字识别示例
文章目录 Github官网文档Playground安装声明张量常量变量 张量计算张量数据类型转换张量数据维度转换ReLU 函数Softmax 函数卷积神经网络训练模型测试模型数据集保存目录显示每层网络的结果 TensorFlow 是一个开源的深度学习框架,由 Google Brain 团队开发和维护。它…...
大数据开发语言Scala入门
Scala是一种多范式编程语言,它集成了面向对象编程和函数式编程的特性。Scala运行在Java虚拟机上,并且可以与Java代码无缝交互,这使得它成为大数据处理和分析领域中非常受欢迎的语言,尤其是在使用Apache Spark这样的框架时。 Scal…...
【CDN】逆天 CDN !BootCDN 向 JS 文件中植入恶意代码
今天在调试代码,突然控制台出现了非常多报错。 这非常可疑,报错指向的域名也证实了这一点。 因为我的 HTML 中只有一个外部开源库(qrcode.min.js),因此只有可能是它出现了问题。 我翻看了请求记录,发现这…...
摆脱Jenkins - 使用google cloudbuild 部署 java service 到 compute engine VM
在之前 介绍 cloud build 的文章中 初探 Google 云原生的CICD - CloudBuild 已经介绍过, 用cloud build 去部署1个 spring boot service 到 cloud run 是很简单的, 因为部署cloud run 无非就是用gcloud 去部署1个 GAR 上的docker image 到cloud run 容…...
【CS.PL】Lua 编程之道: 控制结构 - 进度24%
3 初级阶段 —— 控制结构 文章目录 3 初级阶段 —— 控制结构3.1 条件语句:if、else、elseif3.2 循环语句:for、while、repeat-until3.2.1 输出所有的命令行参数3.2.2 while.lua3.2.3 repeat.lua及其作用域 🔥3.2.4 for.lua (For Statement)…...
从“数据孤岛”、Data Fabric(数据编织)谈逻辑数据平台
提到逻辑数据平台,其核心在于“逻辑”,与之相对的便是“物理”。在过去,为了更好地利用和管理数据,我们通常会选择搭建数据仓库和数据湖,将所有数据物理集中起来。但随着数据量、用数需求和用数人员的持续激增…...
vuex4.x 升级pinia,router 中使用同步组件导致项目启动失败
背景描述 升级的项目本来是vue2的项目,先升级成vue3,这个过程相关的问题都被决绝,当时状态管理使用的还是vuex4.x版本。 后面发现变成复杂模块时,后续再对复杂模块的功能进行迭代时,由于js的弱类型,改动时…...
0. 云原生之基于乌班图远程开发
云原生专栏大纲 文章目录 安装乌班图配置静态IP重置root密码开启root远程登录开启远程SSH访问安装docker安装docker-compose安装Edge浏览器安装搜狗输入法安装TeamViewer安装虚拟显示器安装JDK安装maven安装vscodevscode插件安装VSCode配置maven、git、jdk、自动报错vscode快捷…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
