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快捷…...
C++ 字符串处理5-手机号邮箱如何脱敏处理
1. 关键词2. strutil.h3. strutil.cpp4. 测试代码5. 运行结果6. 源码地址 1. 关键词 关键词: C 字符串处理 分割字符串 连接字符串 跨平台 应用场景: 有些重要信息需要保密,比如手机号、邮箱等,如何在不影响用户阅读的情况下…...
【lesson8】云备份服务端完整版代码
文章目录 util.hppconfig.hpphot.hppdata.hppserver.hppserver.ccMakefilecloud.conf util.hpp #pragma once #include <iostream> #include <fstream> #include <string> #include <vector> #include <sys/stat.h> #include <unistd.h> …...
AI办公自动化:kimi批量搜索提取PDF文档中特定文本内容
工作任务:PDF文档中有资料来源这一行,比如: 资料来源:moomoo tech、The Information、Bloomberg、Reuters,浙商证券研究所 数据来源:CSDN、浙商证券研究所 数据来源:CSDN、arXiv、浙商证券研…...
基于C#开发web网页管理系统模板流程-总集篇
第一篇 基于C#开发web网页管理系统模板流程-登录界面和主界面_c#的网页编程-CSDN博客 第二篇 基于C#开发web网页管理系统模板流程-主界面管理员录入和编辑功能完善_c#网页设计-CSDN博客 第三篇 基于C#开发web网页管理系统模板流程-主界面管理员入库和出库功能完善_c#web程序设计…...
什么是DMZ?路由器上如何使用DMZ?
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 DMZ 📒🚀 DMZ的应用场景💡 路由器设置DMZ🎈 注意事项 🎈⚓️ 相关链接 ⚓️📖 介绍 📖 在网络管理中,DMZ(Demilitarized Zone,隔离区)是一个特殊的网络区域,常用于将公共访问和内部网络隔离开来。DMZ功能允许…...
【bugfix】解决Redis缓存键清理问题
前言 在Spring Boot应用中集成Redis作为缓存存储时,合理配置RedisTemplate是确保数据正确存储和检索的关键。本文将通过对比分析一段初始存在问题的Redis配置代码及其修正后的版本,探讨如何正确处理Redis键前缀,以避免清理缓存时遇到的问题。…...
泛微开发修炼之旅--15后端开发连接外部数据源,实现在ecology系统中查询其他异构系统数据库得示例和源码
文章链接:15后端开发连接外部数据源,实现在ecology系统中查询其他异构系统数据库得示例和源码...
弹幕逆向signature、a_bogus
声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许可禁止转载&a…...
jEasyUI 使用标记创建树形菜单
jEasyUI 使用标记创建树形菜单 jEasyUI 是一个基于 jQuery 的用户界面插件库,它提供了一系列的组件,用于快速构建网页用户界面。其中,树形菜单(Tree Menu)是 jEasyUI 提供的一个非常实用的组件,它可以帮助…...
IT人的拖延——拖是因为不想离开“舒适区”?
人都是求“稳”的,在一个区域内呆了很久,也很舒适了,如果冒险离开进入未知的区域,万一结果不好怎么办?万一自己不适合怎么办?万一这个区域有着自己难以忍受的东西怎么办?这些对未知区域的恐惧感让我们在面对应该要做的事情时,不自觉地又拖延了起来。比如,我们在面临需…...
wordpress全站301/市场营销推广方案模板
点击上方"brucepk",选择"置顶公众号"第一时间关注 Python 技术干货!阅读文本大概需要 3 分钟。自从 pk哥开始认真在公众号分享文章以来,目前已分享了 45 篇原创文章, 昨晚花了几小时对公众号文章进行了整理&a…...
秦皇岛网站制作专家教您简单建站/苏州百度 seo
你好,我是小马总,96年互联网创业者 今天探讨一个创业路上最重要的一个点,合伙生意到底能不能做? 能创业的人都是有魄力的 ,既然你能刷到这篇文章,说明你不是在创业的路上就是有了创业的想法,给…...
自己做的网站怎么置顶/谷歌关键词分析工具
vue 从入门到精通(二) 上一篇总结了一些vue的理论知识,如果你没看懂的话……那返回去继续去看啊!反正我要开始第二篇了。 vue提供了大量的指令,比如:v-if,v-bind,v-on……太多,多写项目,多看API,…...
wordpress评论美化/如何做好精准营销
windows10下升级node.js和降级Nodejs_cheerileeyoki的博客-CSDN博客_win10 升级nodejs...
wordpress 去除google/大连头条热点新闻
本文主要向大家介绍了MySQL数据库之利用sqlmap进行mysql提权的小方法(win与liunx通用) ,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助。1.连接mysql数据打开一个交互shell:sqlmap.py -d mysql://root:root127.0.0.1:3306/test --sql-she…...
网站聊天怎么做/百度知道
软件简介ECharts-X是 ECharts 团队推出的全新 3D 可视化库,它是基于ECharts 的扩展,底层深度整合了 WebGL 库QTEK和 Canvas2D库ZRender。特色混搭ECharts 里的混搭功能很强大,作为 ECharts 的扩展,ECharts-X 自然也需要支持。ECha…...