JavaScript前端提取PDF图片的完整指南

作者:青柠时间:2026-10-31 19:37:52浏览:566

本文详解如何利用JavaScript和PDF.js在前端直接提取PDF文件中的图片。通过结合Web Worker、OffscreenCanvas和ImageBitmap技术,实现异步非阻塞的图片渲染与转换,有效避免主线程卡顿。文章对比了前后端处理的优劣,提供了从文件上传到图片生成的完整代码示例及性能优化建议。

提取PDF图片,指的是从文档的每一页中单独抽取其中的多张图片,而不是将整页内容合并转换为一张大图。

效果展示

效果展示

技术实现详解

本项目采用PDF.js库来解析和渲染PDF文件。该库在GitHub上拥有43.9kb的Star数,属于非常成熟的开源方案。

一、引入pdf.js

在项目中需要同时引入pdf.js核心库和WebWorker。WebWorker的作用是在浏览器的子线程中解析图片等资源,从而避免阻塞主页面的UI渲染和用户交互。引入代码如下:

<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.10.111/pdf.js"></script>

<script>
// pdf.js 从性能考虑,使用了 WebWorker, 在浏览器子线程解析图片等资源,不阻塞页面UI和交互。
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.10.111/build/pdf.worker.js';
</script>

二、上传PDF文件并监听事件

在用户上传文件时,使用FileReader异步读取文件对象。读取完成后,将其转换为Uint8Array格式,并传入pdfjsLib以获取PDF加载任务。

具体实现代码如下:

<input type="file" id="fileInput" name="avatar" title ="选择 pdf 文件"/>

<script>
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener(
  "change",
  () => {
    const file = fileInput.files[0];
    console.log('fileInput file: ', file);
    // 上传文件时,使用 FileReader 对象读取
    var fileReader = new FileReader();
    fileReader.onload = function() {
          // 将文件对象转化为 Uint8Array 
          var typedarray = new Uint8Array(this.result);
          // 返回 PDF 加载任务 PDFDocumentLoadingTask
          const loadingTask = pdfjsLib.getDocument(typedarray);
          // 开始加载 PDF,这里封装了一个函数
          loadPDFFile(loadingTask);
    };

    fileReader.readAsArrayBuffer(file);
  },
  false
);
</script>

三、获取并遍历PDF页数

async function loadPDFFile(loadingTask) {
  // PDF 加载任务
  const pdf = await loadingTask.promise;
  // 获取 PDF 页数
  const numPages = pdf.numPages;
  for (let curPage = 1; curPage <= numPages; curPage++) {
    // 返回当前页
    console.log('loadingServerFile curPage: ', curPage);
    const page = await pdf.getPage(curPage);

    const scale = 1.5;
    // 获取渲染视角尺寸
    const viewport = page.getViewport({ scale });
    // Support HiDPI-screens.
    const outputScale = window.devicePixelRatio || 1;
    // 传入离屏 Canvas
    const canvas = new OffscreenCanvas(200, 200);
    // 获取 Canvas 上下文
    const context = canvas.getContext("2d");
    // 转换尺寸
    const transform = outputScale !== 1 
      ? [outputScale, 0, 0, outputScale, 0, 0] 
      : null;

    const renderContext = {
      canvasContext: context,
      transform,
      viewport,
    };
    // 调用 page.render 触发渲染
    const renderTask = page.render(renderContext);
    // 等待 renderTask 渲染任务
    // await delay(500); // 如果渲染过快,可能导致 CPU 飙升、电脑可用内存不够,可以加延迟减慢速度
    await renderTask.promise;
  }
}

在代码实现中,使用了OffscreenCanvas。这是一个可以脱离屏幕直接渲染的Canvas对象,既可以在主线程使用,也可以在Web Worker子线程中使用,旨在优化性能。虽然提取PDF图片不需要将结果渲染到页面可见区域,但仍需要执行渲染任务。

需要注意的是,如果渲染速度过快,可能会导致CPU占用率飙升或电脑可用内存不足。可以通过添加setTimeout延迟来降低处理速度,以平衡性能。

四、提取每页PDF中的图片

在此步骤中,我们获取了由pdfjs提取出的PDF图片,其格式为ImageBitmap。ImageBitmap是对Canvas上位图数据的引用,数据存储在GPU中。它可以在Web Worker中生成,从而避免影响主线程的UI绘制,也不会阻塞用户对页面的交互响应。

相关代码实现如下:

async function loadPDFFile(loadingTask) {
    // ...接上一步
    const renderTask = page.render(renderContext);
    // 等待当前页渲染任务执行
    await renderTask.promise;
    // 获取操作列表
    const ops = await page.getOperatorList();

    // 提取图片
    const imageNames = ops.fnArray.reduce((acc, curr, i) => {
      if ([pdfjsLib.OPS.paintImageXObject, pdfjsLib.OPS.paintJpegXObject].includes(curr)) {
        acc.push(ops.argsArray[i][0]);
      }
      return acc;
    }, []);
    for (const imageName of imageNames) {
      console.log('imageName: ', imageName);
      page.objs.get(imageName, (image) => {
        // console.log('image: ', image);
        (async function() {          
          const bmp = image.bitmap;
          // create a canvas
          console.log('bmp: ', bmp);
        })();      
      });
    }
}

五、将ImageBitmap转换为Img标签

最后一步,也是关键的一步!─=≡Σ(((つ•̀ω•́)つ

在上一步中,我们获取了Canvas绘图引用的ImageBitmap对象,现在需要将其转换为浏览器中标准的Img图片元素。

具体转换过程为:先将ImageBitmap渲染到Canvas上,然后调用canvas.convertToBlob()方法即可得到Blob对象。

async function loadPDFFile(loadingTask) {
    // ...接上一步,拿到了每页 PDF 的 image.bitmap 对象
    for (const imageName of imageNames) {
      console.log('imageName: ', imageName);
      page.objs.get(imageName, (image) => {
        // console.log('image: ', image);
        (async function() {          
            const bmp = image.bitmap;
            console.log('bmp: ', bmp);
            // OffscreenCanvas
            const resizeScale = 1/4; // 这个可以控制转换后的图片大小
            const width = bmp.width * resizeScale;
            const height = bmp.height * resizeScale;
            const canvas = new OffscreenCanvas(width, height);
            // 获取 canvas bitmaprenderer 上下文
            const ctx = canvas.getContext('bitmaprenderer');
            // 把 ImageBitmap 渲染到 OffscreenCanvas
            ctx.transferFromImageBitmap(bmp);
            // 把 canvas 画布转化为 Blob 对象
            // https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas/convertToBlob
            const blob = await canvas.convertToBlob();
            console.log('blob: ', blob); // blob
            // 最后使用 Blob 作为 URL.createObjectURL 的参数,渲染出 img 图片
            // 如果不需要渲染,则可以讲 Blob 数据上传到云存储
            const img = document.body.appendChild(new Image());
            img.width = width;
            img.height = height;
            img.src = URL.createObjectURL(blob);
        })();      
      });
    }
}

OffscreenCanvas可以放置在Web Worker中创建,这样就不会阻塞主线程的UI绘制和用户交互响应。

使用bitmaprenderer.transferFromImageBitmap()可以实现不拷贝数据、仅传递内存地址的方式,将ImageBitmap高效地传递到Canvas上。

背景(缘起)

前段时间,项目需求中涉及从PDF中提取图片,用于评论展示和审核流程。

由于项目时间紧迫、任务繁重,最初由后端实现了将一页PDF转换为一张图片的功能。

后端处理方案的优劣分析如下:

  • 优点:用户体验较好,不会占用用户本地电脑的性能资源。
  • 劣:加大服务器压力
    • 缺点:占用大量CPU资源。在我使用的Mac笔记本MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)上进行解析时,观察到CPU使用率每次都飙升至100%以上。
    • 缺点:占用大量内存。解析后的图片在上传至CDN之前,一直驻留在服务器内存中未被释放。
    提取流程变长
    • 缺点:需要将PDF文件先上传到服务器,再由后端进行处理,增加了网络传输和服务器负载。

前端处理方案的优劣分析如下:

  • 优点:不占用服务器资源,所有提取操作均在浏览器端完成。
  • 缺点:可能会引起用户电脑出现卡顿现象,取决于设备性能。

以上就是JavaScript提取PDF图片的方法详解的详细内容,更多关于JavaScript提取PDF图片的资料请关注本站其它相关文章!

作者最新文章