当前位置: 首页 > news >正文

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。因为新版本 兼容性不太好&#xff0c;部分手机预览不了&#xff0c;所以采用v2版本。 相关依赖 "canvas": "^2.11.2", "pdfjs-dist": "^2.16.105", "core-js-pure": "^3.37.…...

SSID简介

一、 SSID 概念定义 SSID&#xff08;Service Set Identifier&#xff09;即服务集标识符。它是无线网络中的一个重要标识&#xff0c;用于区分不同的无线网络。 相当于无线网络的名称&#xff0c;用于区分不同的无线网络。用户在众多可用网络中识别和选择特定网络的依据。通…...

PS通过GTX实现SFP网络通信1

将 PS ENET1 的 GMII 接口和 MDIO 接口 通过 EMIO 方 式引出。在 PL 端将引出的 GMII 接口和 MDIO 接口与 IP 核 1G/2.5G Ethernet PCS/PMA or SGMII 连接&#xff0c; 1G/2.5G Ethernet PCS/PMA or SGMII 通过高速串行收发器 GTX 与 MIZ7035/7100 开发…...

前端面试项目细节重难点(已工作|做分享)(九)

面试官&#xff1a;请你讲讲你在工作中如何开发一个新需求&#xff0c;你的整个开发过程是什么样的&#xff1f; 答&#xff1a;仔细想想&#xff0c;我开发新需求的过程如下&#xff1a; &#xff08;1&#xff09;第一步&#xff1a;理解需求文档&#xff1a; 首先&#x…...

区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测

区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现BP-ABKDE的BP神经网络自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现BP-ABKDE的BP神经网络自适应带…...

抢占人工智能行业红利,前阿里巴巴产品专家带你15天入门AI产品经理

前言 当互联网行业巨头纷纷布局人工智能&#xff0c;国家将人工智能上升为国家战略&#xff0c;藤校核心课程涉足人工智能…人工智能领域蕴含着巨大潜力&#xff0c;早已成为业内共识。 面对极大的行业空缺&#xff0c;不少人都希望能抢占行业红利期&#xff0c;进入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的终极指南

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 在Java编程中&#xff0c;经常需要将一个List对象转换为另一个Map对象。这可能是因为需要根据List中的元素的某些属性来创建一个新的键值对集合。在本文中&#xff0c;我将向您展示如何使用Java 中的流式API轻松地实…...

【秋招突围】2024届秋招笔试-小红书笔试题-第一套-三语言题解(Java/Cpp/Python)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系计划跟新各公司春秋招的笔试题 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f4e7; 清隆这边…...

HAL库开发--STM32的HAL环境搭建

知不足而奋进 望远山而前行 目录 文章目录 前言 下载 安装 解压 安装 添加开发包 修改仓库路径 下载软件开发包&#xff08;慢&#xff0c;不推荐&#xff09; 解压已有软件开发包&#xff08;快&#xff0c;推荐&#xff09; 总结 前言 在嵌入式系统开发中&#x…...

【DPDK学习路径】七、创建RX/TX队列

上一节我们讲述了如何申请内存池缓冲区以便接下来创建 RX 队列&#xff0c;这一节我们将给出具体如何创建 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 是一个开源的深度学习框架&#xff0c;由 Google Brain 团队开发和维护。它…...

大数据开发语言Scala入门

Scala是一种多范式编程语言&#xff0c;它集成了面向对象编程和函数式编程的特性。Scala运行在Java虚拟机上&#xff0c;并且可以与Java代码无缝交互&#xff0c;这使得它成为大数据处理和分析领域中非常受欢迎的语言&#xff0c;尤其是在使用Apache Spark这样的框架时。 Scal…...

【CDN】逆天 CDN !BootCDN 向 JS 文件中植入恶意代码

今天在调试代码&#xff0c;突然控制台出现了非常多报错。 这非常可疑&#xff0c;报错指向的域名也证实了这一点。 因为我的 HTML 中只有一个外部开源库&#xff08;qrcode.min.js&#xff09;&#xff0c;因此只有可能是它出现了问题。 我翻看了请求记录&#xff0c;发现这…...

摆脱Jenkins - 使用google cloudbuild 部署 java service 到 compute engine VM

在之前 介绍 cloud build 的文章中 初探 Google 云原生的CICD - CloudBuild 已经介绍过&#xff0c; 用cloud build 去部署1个 spring boot service 到 cloud run 是很简单的&#xff0c; 因为部署cloud run 无非就是用gcloud 去部署1个 GAR 上的docker image 到cloud run 容…...

【CS.PL】Lua 编程之道: 控制结构 - 进度24%

3 初级阶段 —— 控制结构 文章目录 3 初级阶段 —— 控制结构3.1 条件语句&#xff1a;if、else、elseif3.2 循环语句&#xff1a;for、while、repeat-until3.2.1 输出所有的命令行参数3.2.2 while.lua3.2.3 repeat.lua及其作用域 &#x1f525;3.2.4 for.lua (For Statement)…...

从“数据孤岛”、Data Fabric(数据编织)谈逻辑数据平台

提到逻辑数据平台&#xff0c;其核心在于“逻辑”&#xff0c;与之相对的便是“物理”。在过去&#xff0c;为了更好地利用和管理数据&#xff0c;我们通常会选择搭建数据仓库和数据湖&#xff0c;将所有数据物理集中起来。但随着数据量、用数需求和用数人员的持续激增&#xf…...

vuex4.x 升级pinia,router 中使用同步组件导致项目启动失败

背景描述 升级的项目本来是vue2的项目&#xff0c;先升级成vue3&#xff0c;这个过程相关的问题都被决绝&#xff0c;当时状态管理使用的还是vuex4.x版本。 后面发现变成复杂模块时&#xff0c;后续再对复杂模块的功能进行迭代时&#xff0c;由于js的弱类型&#xff0c;改动时…...

0. 云原生之基于乌班图远程开发

云原生专栏大纲 文章目录 安装乌班图配置静态IP重置root密码开启root远程登录开启远程SSH访问安装docker安装docker-compose安装Edge浏览器安装搜狗输入法安装TeamViewer安装虚拟显示器安装JDK安装maven安装vscodevscode插件安装VSCode配置maven、git、jdk、自动报错vscode快捷…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...