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

pdf.js在iOS移动端分页加载优化方案(ios移动端反复刷新加载问题)

背景与问题

在iOS移动端加载大型PDF文件时,由于设备内存限制,经常遇到以下问题:

  • 内存不足导致页面崩溃
  • 大文件加载缓慢
  • 页面反复重新加载

##解决方案

采用PDF.js的分页加载策略,实现按需加载当前可视页面及相邻页面,减少内存占用。

核心实现代码

let pdfDoc: pdf.PDFDocumentProxy;
let currentVisiblePage = 1;
let isScrolling = false;async function loadPdf(url: string) {try {// 先下载为 Blob(兼容 iOS 缓存)const blob = await fetch(url).then((res) => res.blob());const blobUrl = URL.createObjectURL(blob);const loadingTask = pdf.getDocument({url: blobUrl,disableAutoFetch: true,disableStream: true,disableRange: true,useSystemFonts: true,});pdfDoc = await loadingTask.promise;await loadVisiblePages();window.addEventListener("scroll", handleScroll, { passive: true });} catch (error) {console.error("PDF加载失败:", error);}
}

关键技术点

1. 分页加载策略

  • 初始化加载:仅加载第一页
  • 滚动监听:动态加载当前可视页面
  • 预加载:同时加载当前页后2页,提升浏览体验
async function loadVisiblePages() {if (!pdfDoc) return;const startPage = currentVisiblePage;const endPage = Math.min(pdfDoc.numPages, currentVisiblePage + 2);for (let i = startPage; i <= endPage; i++) {if (!document.getElementById(`the-canvas${i}`)) {await renderPage(i);}}
}

2. 滚动优化处理

  • 使用requestAnimationFrame优化滚动性能
  • 防抖处理避免频繁计算
function handleScroll() {if (isScrolling) return;isScrolling = true;requestAnimationFrame(async () => {const newPage = calculateCurrentPage();if (newPage !== currentVisiblePage) {currentVisiblePage = newPage;await loadVisiblePages();}isScrolling = false;});
}

3. 页面位置计算

基于视口中心点计算当前最接近的页面:

function calculateCurrentPage(): number {const scrollPosition = window.scrollY || window.pageYOffset;const viewportCenter = scrollPosition + window.innerHeight / 2;let closestPage = currentVisiblePage;let minDistance = Infinity;canvases.forEach((canvas) => {const pageNum = parseInt(canvas.id.replace("the-canvas", ""));const rect = canvas.getBoundingClientRect();const pageCenter = (rect.top + rect.bottom) / 2 + scrollPosition;const distance = Math.abs(pageCenter - viewportCenter);if (distance < minDistance) {minDistance = distance;closestPage = pageNum;}});return Math.max(1, Math.min(closestPage, pdfDoc.numPages));
}

4. 内存管理

虽然注释掉了卸载逻辑,但保留了卸载能力:

function unloadPage(pageNum: number) {const canvas = document.getElementById(`the-canvas${pageNum}`);if (canvas) {const page = (canvas as any)._pdfPage;if (page) {page.cleanup();page._destroy();}canvas.remove();}
}

性能优化措施

  1. PDF加载配置

    • disableAutoFetch: true - 禁用自动获取
    • disableStream: true - 禁用流式加载
    • disableRange: true - 禁用范围请求
    • useSystemFonts: true - 使用系统字体
  2. 渲染优化

    • 动态计算canvas尺寸适配屏幕
    • 使用CSS控制canvas显示样式
canvas.style.width = `${document.body.clientWidth}px`;
canvas.style.height = `${document.body.clientWidth / (canvas.width / canvas.height)}px`;

总结

该方案通过以下方式解决了iOS移动端PDF加载问题:

  • 分页按需加载降低内存占用
  • 智能预加载提升用户体验
  • 优化的滚动计算确保流畅性
  • 完善的错误处理增强稳定性

对于超大PDF文件,可考虑进一步优化:

  1. 实现页面卸载逻辑
  2. 添加LRU缓存策略
  3. 支持更精细的缩放级别控制

完整代码

import * as pdf from 'pdfjs-dist';pdf.GlobalWorkerOptions.workerSrc = 'path/to/pdf.worker.js';let pdfDoc: pdf.PDFDocumentProxy;
let currentVisiblePage = 1;
let isScrolling = false;async function loadPdf(url: string) {try {// 先下载为 Blob(兼容 iOS 缓存)const blob = await fetch(url).then((res) => res.blob());const blobUrl = URL.createObjectURL(blob);const loadingTask = pdf.getDocument({url: blobUrl,disableAutoFetch: true,disableStream: true,disableRange: true,useSystemFonts: true,});pdfDoc = await loadingTask.promise;// 初始化加载第一页await loadVisiblePages();// 添加滚动监听window.addEventListener("scroll", handleScroll, { passive: true });} catch (error) {console.error("PDF加载失败:", error);}
}function handleScroll() {if (isScrolling) return;isScrolling = true;requestAnimationFrame(async () => {const newPage = calculateCurrentPage();if (newPage !== currentVisiblePage) {currentVisiblePage = newPage;await loadVisiblePages();}isScrolling = false;});
}function calculateCurrentPage(): number {if (!pdfDoc || !document.getElementById("pdfViewerPages")) {return currentVisiblePage;}const scrollPosition = window.scrollY || window.pageYOffset;const pdfContainer = document.getElementById("pdfViewerPages")!;const containerTop = pdfContainer.offsetTop;const relativeScroll = scrollPosition - containerTop;const viewportCenter = relativeScroll + window.innerHeight / 2;const canvases = Array.from(document.querySelectorAll('canvas[id^="the-canvas"]'));// 找出距离视口中心最近的页面let closestPage = currentVisiblePage;let minDistance = Infinity;canvases.forEach((canvas) => {const pageNum = parseInt(canvas.id.replace("the-canvas", ""));const rect = canvas.getBoundingClientRect();const pageTop = rect.top + scrollPosition - containerTop;const pageBottom = rect.bottom + scrollPosition - containerTop;const pageCenter = (pageTop + pageBottom) / 2;const distance = Math.abs(pageCenter - viewportCenter);if (distance < minDistance) {minDistance = distance;closestPage = pageNum;}});return Math.max(1, Math.min(closestPage, pdfDoc.numPages));
}async function loadVisiblePages() {if (!pdfDoc) return;// 加载可见页(当前页及后两页)const startPage = currentVisiblePage;const endPage = Math.min(pdfDoc.numPages, currentVisiblePage + 2);for (let i = startPage; i <= endPage; i++) {if (!document.getElementById(`the-canvas${i}`)) {try {await renderPage(i);} catch (error) {console.error(`渲染第 ${i} 页失败:`, error);}}}// 下载完成时,loading消失loading.value = false;
}async function renderPage(pageNum: number) {const page = await pdfDoc.getPage(pageNum);const canvas = document.createElement("canvas");canvas.id = `the-canvas${pageNum}`;canvas.className = "pdf-page";const scaledViewport = page.getViewport({ scale: 1 }); // 缩放后的视口canvas.height = Math.floor(scaledViewport.height); // 设置画布的高度canvas.width = Math.floor(scaledViewport.width); // 设置画布的宽度canvas.style.width = `${document.body.clientWidth}px`; // 设置画布的宽度canvas.style.height = `${document.body.clientWidth / (canvas.width / canvas.height)}px`; // 设置画布的高度// 设置canvas样式Object.assign(canvas.style, {display: "block",margin: "10px auto",boxShadow: "0 2px 5px rgba(0,0,0,0.1)",});await page.render({canvasContext: canvas.getContext("2d")!,viewport: scaledViewport,}).promise;document.getElementById("pdfViewerPages")?.appendChild(canvas);(canvas as any)._pdfPage = page;
}function unloadPage(pageNum: number) {const canvas = document.getElementById(`the-canvas${pageNum}`);if (canvas) {const page = (canvas as any)._pdfPage;if (page) {try {page.cleanup();page._destroy();} catch (e) {console.warn(`卸载页面 ${pageNum} 时出错:`, e);}}canvas.remove();}
}
http://www.xdnf.cn/news/13386.html

相关文章:

  • dedecms 织梦自定义表单留言增加ajax验证码功能
  • 传统影像的盲区:心血管疾病诊断的新突破与未来
  • H5流媒体播放器EasyPlayer.js对H.265编码MP4文件的播放支持应用方案
  • C++_核心编程_多态案例二-制作饮品
  • 【JVM】- 垃圾回收
  • 字符串方法_indexOf() +_trim()+_split()
  • 6.10 - 常用 SQL 语句以及知识点
  • 【threejs】每天一个小案例讲解:常见几何体
  • Android --- Handler的用法,子线程中怎么切线程进行更新UI
  • 清华大学视觉空间智能新突破!Spatial-MLLM:提升多模态大语言模型的视觉空间智能能力
  • 3通道图的数据在opencv的mat是如何存放的
  • flow_controllers
  • plantuml画uml图
  • Python实例题:Python计算离散数学
  • 使用swagger来生成文档
  • C++中优雅的属性封装:Sint类设计分析
  • 网络六边形受到攻击
  • PLC入门【5】基本指令3(PLS PLF ZRST)
  • TestCafe API
  • vue3 + element plus -- table表格使用sortablejs实现表格拖拽换位功能
  • 麒麟Kylin V10 SP3服务器操作系统安装
  • 项目进度管理软件是什么?项目进度管理软件有哪些核心功能?
  • LoRA(Low-Rank Adaptation,低秩适应)
  • leetCode- 两数相加
  • 【AI学习】一、向量表征(Vector Representation)
  • 报告精读:金融算力基础设施发展报告 2024【附全文阅读】
  • 构建欺诈事件的结构化威胁建模框架
  • Coze 和 Dify 对比
  • 销售心得分享
  • 保险风险预测数据集insurance.csv