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. 使用
javascript 复制代码
<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 文件 URL
  loadPdf(pdfUrl)
  window.addEventListener('scroll', handleScroll) // 滚动变化时更新条件
})
// 清理事件监听器
onUnmounted(() => {
  window.removeEventListener('scroll', handleScroll)
  if (pdf) {
    pdf.destroy()
  }
})
// 加载pdf
async function loadPdf(url) {
  pdfjsLib.GlobalWorkerOptions.workerPort = pdfjsWorker
  try {
    const loadingTask = pdfjsLib.getDocument(url)
    pdf = await loadingTask.promise
    pdfPages.value = Number(pdf.numPages)
    pdfLoading.value = false
    await nextTick(() => {
      handleScroll()
    })
  } catch (err) {
    pdfLoading.value = false
    console.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.height
      canvas.width = viewport.width

      await page.render({ canvasContext: ctx, viewport }).promise
      // 将画布内容缓存为图片
      const blobURL = await canvasToBlobURL(canvas)
      const image = new Image()
      image.src = blobURL// 将画布内容转换为 Blob URL
      image.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() {
  // pdf
  const windowHeight = window.innerHeight
  const currentScrollY = window.scrollY
  // 判断往上滑动还是往下
  scrollDirection.value = currentScrollY > lastScrollY.value ? 'down' : 'up'
  lastScrollY.value = currentScrollY

  const 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 = true
            return renderPage(next) // 如果页面未加载,则加载该页
          }
        }
      }
    })
  }, Promise.resolve())
}
</script>
相关推荐
狼性书生1 小时前
uniapp 实现的下拉菜单组件
前端·uni-app·vue·组件·插件
程序员瓜叔2 小时前
PDF文件转Markdown,基于开源项目marker
ai·pdf
2301_764441334 小时前
olmOCR大模型:支持结构化精准提取复杂PDF文件内容
python·pdf·ocr
shuang_199516 小时前
VSCode创建VUE项目(三)使用axios调用后台服务
前端·vue
AI航海家(Ethan)1 天前
使用Python轻松拆分PDF,每页独立成文件
python·pdf
幸福清风1 天前
【OCR】使用Umi-OCR进行PDF文档的光学字符识别
pdf·ocr
Macdo_cn2 天前
PDF Reader Pro for Mac v4.9.0 PDF编辑/批注/OCR/转换工具 支持M、Intel芯片
pdf·ocr
GoTime83452 天前
PDFMathTranslate 安装、使用及接入deepseek
python·pdf·翻译·论文翻译·deepseek·pdftranslate
YEAH!启动!2 天前
WPS二次开发系列:WPS SDK事件回调
android·java·前端·pdf·word·wps·ppt