pdf文件分页按需查看

pdf预览本来打算粗暴点,一次性查看全部,但是一个pdf四五百页导致手机端查看超出内存直接崩掉,崩掉会导致页面疯狂刷新,所以不得不进行优化
解决思路大致如下:

  1. canvas转为blob格式以图片的形式加载在页面(Blob URL 是基于磁盘的临时文件,可以减少内存占用)
  2. 分段按需加载,根据页面滑动位置决定加载哪页数据
  3. 历史pdf加载数据缓存,避免一直调用获取pdf逻辑,但不可缓存全部历史数据,变量数据过多也会导致崩掉
  4. 及时移除画布和图片,确保内存被释放

具体实现看代码吧!对了,我这里用的是vue3框架,方案大致都差不多,可供参考

  1. 安装 pdfjs-dist包(版本为4.10.38)
    npm install pdfjs-dist
  2. 使用
<template><div class="pdf-box"><van-loading v-if="pdfLoading && !isHasFirstPage" size="24px">内容正在玩命加载中,请稍后...</van-loading><div :style="(pdfLoading && !isHasFirstPage)?'position:fixed;transform: translateX(-200%);':''"><div v-for="page in pdfPages" :id="'__pdf_canvas_page_' + page" :key="page" /></div><van-loading v-if="pdfItemLoading && isHasFirstPage" size="24px" style="padding-top:0">滑慢点~,小的加载不过来啦...</van-loading></div>
</template><script setup>
import { ref, onMounted, nextTick, onUnmounted } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'
import Worker from 'pdfjs-dist/build/pdf.worker.min.mjs?worker'const pdfjsWorker = new Worker()
let pdf = null
const pdfPages = ref(0) // pdf总页数
const pdfLoading = ref(true) // pdf 总页数获取loading
let cachedPages = new Map() // pdf历史 图片信息缓存
const pdfItemLoading = ref(false) // pdf 单独页面加载
const MAX_CACHED_PAGES = 6 // 最大缓存页面数
const lastScrollY = ref(0) // 最后一次滚动位置
const scrollDirection = ref('') // 页面滚动方向 up:上 down:下
const isHasFirstPage = ref(false) // 第一页是否已加载出来// 在组件挂载后加载 PDF
onMounted(() => {const pdfUrl = 'https://example.com/sample.pdf' // 替换为你的 PDF 文件 URLloadPdf(pdfUrl)window.addEventListener('scroll', handleScroll) // 滚动变化时更新条件
})
// 清理事件监听器
onUnmounted(() => {window.removeEventListener('scroll', handleScroll)if (pdf) {pdf.destroy()}
})
// 加载pdf
async function loadPdf(url) {pdfjsLib.GlobalWorkerOptions.workerPort = pdfjsWorkertry {const loadingTask = pdfjsLib.getDocument(url)pdf = await loadingTask.promisepdfPages.value = Number(pdf.numPages)pdfLoading.value = falseawait nextTick(() => {handleScroll()})} catch (err) {pdfLoading.value = falseconsole.error(err)}
}
// 渲染指定页面pdf
const renderPage = (pageNumber) => {return new Promise(() => {(async () => {const page = await pdf.getPage(pageNumber)const viewport = page.getViewport({ scale: 1.5 })const canvas = document.createElement('canvas')const ctx = canvas.getContext('2d')canvas.height = viewport.heightcanvas.width = viewport.widthawait page.render({ canvasContext: ctx, viewport }).promise// 将画布内容缓存为图片const blobURL = await canvasToBlobURL(canvas)const image = new Image()image.src = blobURL// 将画布内容转换为 Blob URLimage.id = `page-image-${pageNumber}`image.style.width = '100%'// 将图片添加到容器中const container = document.getElementById(`__pdf_canvas_page_${pageNumber}`)container.innerHTML = ''container.appendChild(image)// 缓存 Blob URL 和 Image 标签cachePage(pageNumber, { blobURL, image: image.src })pdfItemLoading.value = false// resolve()})()})
}
// 缓存页面
const cachePage = (pageNumber, data) => {cachedPages.set(pageNumber, data)if (cachedPages.size > 0) { // 第一页是否已加载出来isHasFirstPage.value = true}if (cachedPages.size > MAX_CACHED_PAGES) {// 如果缓存数量超过限制cachedPages = new Map([...cachedPages.entries()].sort((a, b) => a[0] - b[0])) // 排序let oldestPage = ''if (scrollDirection.value === 'up') { // 往上滑动 移除最后的页面const keysArray = [...cachedPages.entries()]oldestPage = keysArray[keysArray.length - 1][0]} else { // 往上滑动 移除最早的页面oldestPage = cachedPages.keys().next().value}if (oldestPage && pageNumber !== oldestPage) {unloadPage(oldestPage)}}
}
// 将画布内容转换为 Blob URL
const canvasToBlobURL = (canvas) => {return new Promise((resolve) => {canvas.toBlob((blob) => {const url = URL.createObjectURL(blob)resolve(url)})})
}
// 清除非可视pdf信息
const unloadPage = (pageNumber) => {const container = document.getElementById(`__pdf_canvas_page_${pageNumber}`)const canvas = document.getElementById(`page-${pageNumber}`)const image = document.getElementById(`page-image-${pageNumber}`)const cachedData = cachedPages.get(pageNumber)if (cachedData) {const rect = container.getBoundingClientRect()container.innerHTML = ''const div = document.createElement('div')div.style.height = rect.height + 'px'container.appendChild(div)if (cachedData.blobURL) {URL.revokeObjectURL(cachedData.blobURL)} // 释放 Blob URL}if (canvas) {canvas.remove()} // 移除画布if (image) {image.remove()} // 移除图片// 从缓存中移除页面cachedPages.delete(pageNumber)
}
// 处理滚动事件
function handleScroll() {// pdfconst windowHeight = window.innerHeightconst currentScrollY = window.scrollY// 判断往上滑动还是往下scrollDirection.value = currentScrollY > lastScrollY.value ? 'down' : 'up'lastScrollY.value = currentScrollYconst pdfPagesArr = Array.from({ length: pdfPages.value }, (_, i) => i + 1)pdfPagesArr.reduce((accumulatorPromise, next) => {return accumulatorPromise.then(() => {	// 上一个接口执行完毕再执行下一个const pageElement = document.getElementById('__pdf_canvas_page_' + next)if (pageElement) {const rect = pageElement.getBoundingClientRect()if (!pdfItemLoading.value) {if ((scrollDirection.value === 'up' ? rect.top < windowHeight + 200 : rect.top < windowHeight) && rect.bottom > 0) {if (cachedPages.has(next)) { // 已加载则跳过return}pdfItemLoading.value = truereturn renderPage(next) // 如果页面未加载,则加载该页}}}})}, Promise.resolve())
}
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/898716.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算力100问☞第92问:为什么各地热衷建设算力中心?

目录 1、宏观分析 2、政府角度分析 3、投资者角度分析 在数字化浪潮中,各地对算力中心建设的热情高涨,这一现象背后潜藏着诸多深层次的原因,涵盖了经济、科技、社会等多个维度,且彼此交织,共同驱动着这一发展趋势。 1、宏观分析 从经济结构转型的底层逻辑来看,全球经…

Redis 内存管理

Redis 内存管理 1. Redis 给缓存数据设置过期时间的作用 给缓存数据设置过期时间&#xff08;TTL, Time-To-Live&#xff09;有以下几个重要作用&#xff1a; (1) 自动释放内存 避免缓存数据无限增长&#xff0c;导致 Redis 内存溢出。例如&#xff0c;在 会话管理、短连接…

PyCharm中使用pip安装PyTorch(从0开始仅需两步)

无需 anaconda&#xff0c;只使用 pip 也可以在 PyCharm 集成环境中配置深度学习 PyTorch。 本文全部信息及示范来自 PyTorch 官网。 以防你是super小白&#xff1a; PyCharm 中的命令是在 Python Console 中运行&#xff0c;界面左下角竖排图标第一个。 1. 安装前置包 numpy …

掌握新编程语言的秘诀:利用 AI 快速上手 Python、Go、Java 和 Rust

网罗开发 &#xff08;小红书、快手、视频号同名&#xff09; 大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等…

如何理解java中Stream流?

在Java中&#xff0c;Stream 是 Java 8 引入的一个强大API&#xff0c;用于处理集合&#xff08;如 List、Set、Map 等&#xff09;数据的流式操作。它提供了一种声明式、函数式的编程风格&#xff0c;可以高效地进行过滤、映射、排序、聚合等操作。 Stream 的核心概念 流&…

【Vitis AIE】FPGA快速部署ConvNet 示例MNIST数据集

AIE-ML 上的 MNIST ConvNet 版本&#xff1a;Vitis 2024.2 简介 本教程在 AMD VersalTM 自适应 SoC AIE-ML 上实现了一个卷积神经网络分类器&#xff0c;用于识别来自 MNIST 数据库 的手写数字。目标是说明如何将一个简单的机器学习示例分区和向量化到 Versal AI 引擎。MNIS…

ubuntu桌面图标异常——主目录下的所有文件(如文档、下载等)全部显示在桌面

ubuntu桌面图标异常 问题现象问题根源系统级解决方案方法一:全局修改(推荐多用户环境)方法二:单用户修改(推荐个人环境)操作验证与调试避坑指南扩展知识参考文档问题现象 主目录文件异常显示 用户主目录(如/home/user/)下的所有文件(如文档、下载等)全部显示在桌面,…

OceanBase 4.3.3 AP 解析:应用 RoaringBitmaps 类型处理海量数据的判重和基数统计

对于大数据开发人员而言&#xff0c;处理海量数据的判重操作和基数统计是常见需求&#xff0c;而 RoaringBitmap类型及其相关函数是当前非常高效的一种解决方案&#xff0c;许多大数据库产品已支持RoaringBitmap类型。OceanBase 4.3.3版本&#xff0c;作为专为OLAP场景设计的正…

W25Qxx

概述 FLASH FLASH是一种是非易失性存储器&#xff0c;即掉电后不会丢失数据&#xff0c;这和RAM&#xff08;随机存储器&#xff09;不同。 FLASH比起同作用的EEPROM有价格低的优点 FLASH的擦除操作是以扇区为单位的&#xff08;比起EEPROM来说操作较为不方便&#xff09; 芯片…

(滑动窗口)算法训练篇11--力扣3.无重复字符的最长字串(难度中等)

目录 1.题目链接&#xff1a;3.无重复字符的最长字符 2.题目描述&#xff1a; 3.解法(滑动窗口)&#xff1a; 1.题目链接&#xff1a;3.无重复字符的最长字符 2.题目描述&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 示例…

深度学习1—Python基础

深度学习1—python基础 你的第一个程序 print(hello world and hello deep learning!)基本数据结构 空值 (None)&#xff1a;在 Python 中&#xff0c;None 是一个特殊的对象&#xff0c;用于表示空值或缺失的值。它不同于数字 0&#xff0c;因为 0 是一个有意义的数字&#…

记一次MyBatis分页莫名其妙的失效,首次执行合适,后续执行分页失效且异常

代码几乎一样&#xff0c;为啥这个xml配置的就会出现莫名其妙的问题呢 org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{propertymybatis_plus_first, modeI…

网络不可达

导致此问题原因较多&#xff0c;我只针对一种情况进行讨论&#xff0c;如果和文中症状不同&#xff0c;另寻他处&#xff0c;或者死马当活马医&#xff08;&#xff1f;&#xff09; 如需转载&#xff0c;标记出处 症状&#xff1a; 1.ping命令网络不可达 2.ifconfig中网卡en…

【AI News | 20250322】每日AI进展

AI Repos 1、DeTikZify 可以把草图或图形转换成TikZ代码的模型&#xff0c;可用来绘制复杂的科学图表&#xff0c;输入草图或文字描述即可转换成TikZ代码。DeTikZify强大的地方在于它能理解图表的语义信息&#xff0c; 能识别图表中的不同组成部分及其含义&#xff0c;比如坐标…

Debian12生产环境配置笔记

在 Debian 12 上进行生产环境配置的详细步骤&#xff0c;涵盖软件更新、基础软件安装、Docker 及 Redis 部署&#xff0c;以及 Nginx 配置多个虚拟主机等内容。所有命令均以 root 用户身份执行&#xff0c;无需添加 sudo 1. 更新软件 首先&#xff0c;确保系统上的所有软件包…

UE AI 模型自动生成导入场景中

打开小马的weix 关注下 搜索“技术链” 回复《《动画》》 快速推送&#xff1b; 拿到就能用轻松解决&#xff01;帮忙点个关注吧&#xff01;

【最后203篇系列】022 用Deepseek14b提取新闻事件

这算是之前一直想做的一件事&#xff0c;趁周末赶快做了。 业务意义&#xff1a;现实中有大量的舆情&#xff0c;这对我们的决策会有比较重要的作用 技术依赖&#xff1a; 1 模型基础能力2 消息队列3 异步获取消息4 时间序列库 1 模型基础能力 大模型发展到现在&#xff0…

电池电量检测方法介绍,开路电压法、库仑积分法、内阻法

开路电压法、库仑积分法、内阻法、卡尔曼滤波法、混合法 开路电压法是目前最简单的方法&#xff0c;根据电池的特性得知&#xff0c;在电池容量与开路电压之间存在一定的函数关系&#xff0c;当得知开路电压时&#xff0c;可以初步估算电池的剩余电量。该方法精度不高&#xf…

微调实战 - 使用 Unsloth 微调 QwQ 32B 4bit (单卡4090)

本文参考视频教程&#xff1a;赋范课堂 – 只需20G显存&#xff0c;QwQ-32B高效微调实战&#xff01;4大微调工具精讲&#xff01;知识灌注问答风格微调&#xff0c;DeepSeek R1类推理模型微调Cot数据集创建实战打造定制大模型&#xff01; https://www.bilibili.com/video/BV1…

【Elasticsearch】基于 Word2Vec 实现文章抄袭检测

🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s…