PDF.js 文件预览实战:静态资源、Canvas 渲染与分片加载优化

作者:巡夜人

时间:2026-10-26

浏览:524

本文总结 PDF.js 在 Web 项目中的两种主要使用方式:静态资源引入与 npm 模块导入。重点解析了通过 Canvas 渲染 PDF 页面时的尺寸适配问题,以及针对大文件白屏现象的分片下载(Range 请求)优化方案。文章详细说明了如何通过配置 getDocument API、处理 HTTP 206 状态码及跨域响应头 Access-Control-Expose-Headers 来确保分片加载的稳定性,并提供了完整的代码实现思路与注意事项。

文章目录

PDF.js 提供了两种主要的集成方式供开发者选择。

在当前项目中,我们需要实现 PDF 文件的在线预览功能,并支持滑动浏览所有页面,因此最终选定 PDF.js 作为解决方案。

  • 第一种方式是通过 npm 包管理器进行安装。
  • 第二种方式是直接下载 PDF.js 库文件,将其作为静态资源嵌入项目。

以下重点介绍将 PDF.js 作为静态资源使用的具体步骤。

项目初期,我采用了静态资源引入的方式,具体操作流程如下:

  • 首先从官网下载 PDF.js 并解压项目文件。
  • 将解压后的文件放置到项目的 /public/static/ 目录下。
  • 接着,直接访问 web/viewer.html,并在 URL 参数 file 后拼接你自己的 PDF 文件路径即可实现预览。
    contentUrl.current = content.includes("pdf")
    ? `/static/pdfjs-3.1.81-dist/web/viewer.html?file=${content}`
    : content;
    <iframe src={contentUrl.current}></iframe>

需要注意的是,如果使用静态资源方式且需要修改默认样式,必须手动修改源代码。

此外,可能会遇到跨域问题,此时需要注释掉源码中关于跨域的判断逻辑。

PDF.js 提供了两种主要的集成方式供开发者选择。

使用静态资源方式的一个主要缺陷是移动端不支持手势放大缩小,这需要我们自行编写代码并修改源码以强制支持缩放功能。

关于静态资源的具体用法,网上已有大量教程,此处不再赘述,仅做简要介绍。

第二种方式是通过 npm 下载,并通过 import 语句在代码中引入。

具体的实现方法如下所示:

npm install pdfjs-dist
const contentRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
//content就是 iframe 请求的url,这里是因为我的项目里面需要判断下他url是否涵盖了pdf
//如果涵盖了才使用pdf.js
  content.includes('pdf')
  //重点是 loadPdf() 这个函数,就是我们使用 pdf.js 的函数
    ? loadPdf(contentRef.current, content, loadingRef.current)
    : null;
}, [content]);
<div className="content-wrapper" ref={contentRef}>
  {content.includes('pdf') ? null : <iframe src={content}></iframe>}
  <div className="loading" ref={loadingRef} style={{ display: 'none' }}></div>
</div>
import * as pdf from 'pdfjs-dist';
import pdfWorker from 'pdfjs-dist/build/pdf.worker.js?url';
pdf.GlobalWorkerOptions.workerSrc = pdfWorker;
/**
 * @desc 使用pdf.js加载pdf
 * @param contentDom
 * @param url
 */
export const loadPdf = async (
  contentDom: HTMLDivElement | null,
  url: string,
  loadingDom: HTMLDivElement | null
) => {
//得到请求的 pdf 文件
  const loadingTask = pdf.getDocument({
    url: url,
    disableRange: true
  });
  //loading效果,下载pdf过程中展示loading
  loadingTask.onProgress = () => {
    if (loadingDom) {
      loadingDom.style.display = 'block';
    }
  };
  loadingTask.promise.then((pdfDoc) => {
  //下载完成时,loading消失
    if (loadingDom) {
      loadingDom.style.display = 'none';
    }
    //得到 pdf 总页数
    const totalPages = pdfDoc.numPages;
    for (let i = 1; i <= totalPages; i++) {
      pdfDoc.getPage(i).then((page) => {
        const canvas = document.createElement('canvas');
        canvas.setAttribute('id', `the-canvas${i}`);
        const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
        const dpr = window.devicePixelRatio || 1;
        const scaledViewport = page.getViewport({ scale: 1 });
        canvas.height = Math.floor(scaledViewport.height * dpr);
        canvas.width = Math.floor(scaledViewport.width * dpr);
        canvas.style.width = document.body.clientWidth + 'px';
        canvas.style.height =
          document.body.clientWidth / (canvas.width / canvas.height) + 'px';
        const transform = dpr !== 1 ? [dpr, 0, 0, dpr, 0, 0] : undefined;
        const renderContext = {
          canvasContext: ctx,
          viewport: scaledViewport,
          transform: transform
        };
        page.render(renderContext);
        contentDom?.appendChild(canvas);
      });
    }
  });
};

上述代码展示了使用 PDF.js 的完整流程:首先获取 PDF 文件,获取总页数后,生成对应数量的 Canvas 元素,最后将页面渲染到 Canvas 上并展示在页面上。

这里需要特别注意,scaledViewport.height 返回的是 PDF 文件原本的宽高尺寸。为了适配我们的屏幕并保证清晰度,必须确保 Canvas 的宽高比例与 PDF 原始尺寸一致。

Canvas 元素包含两套宽高属性:标签的 width 和 height 定义的是绘图区域的实际像素尺寸,所有图形绘制均基于此;而 style 的 width 和 height 定义的是 Canvas 在浏览器中渲染的视觉大小。如果未在标签中明确定义 width 和 height,默认值为宽 300 高 150,这会导致图像拉伸变形。因此,建议在标签中显式指定宽高属性以避免默认值带来的问题。

如代码所示,我们将 Canvas 的宽度设置为屏幕宽度,而每个 PDF 页面的高度则通过 canvas.width / canvas.height 的比例进行计算,以确保尺寸一致。

canvas.style.height = document.body.clientWidth / (canvas.width / canvas.height) + 'px';

通过上述配置,即可成功展示 PDF 文件。

然而,当文件体积较大时,PDF.js 的渲染机制要求先下载整个文件再进行展示,这会导致较长的白屏时间,严重影响用户体验。为此,我们引入了分片下载机制。

我们需要在 getDocument() API 中增加相关配置项。

const loadingTask = pdf.getDocument({
		url: url,
		//disableRange: true,  
		rangeChunkSize: 65536 * 16,
		disableAutoFetch: true
	});

分片下载的关键在于判断目标 PDF 是否支持分片传输。支持分片的请求在后续交互中会通过设定的分片大小发送 206 状态码请求。

HTTP 206 状态码表示请求已成功处理,但服务器仅返回了部分内容,即 Partial Content 响应。

HTTP 206 是 HTTP 协议中的一种响应状态码,属于 2xx 成功系列,表明服务器正在响应部分请求。

PDF.js 提供了两种主要的集成方式供开发者选择。

接下来介绍如何检查服务器是否支持分片下载。

PDF.js 提供了两种主要的集成方式供开发者选择。

服务器响应头中必须包含 accept-range:bytes,才表示支持分片下载。

但在实际测试中我们发现,即使响应头中存在 accept-range,仍无法成功分片下载。经过对比排查,问题出在 access-control-expose-headers 响应头上。

Access-Control-Expose-Headers 响应头用于告知浏览器,哪些响应头信息可以暴露给运行在脚本中的跨源请求。

最初,该响应头中未包含 accept-range 相关信息,这意味着即使服务器支持分片,浏览器也无法读取该头部信息。因此,我们在后端添加了包含 accept-range 的 Access-Control-Expose-Headers 响应头后,分片下载功能即可正常使用。

以下记录相关 API 参数,以备后续查阅。

此部分 API 参考自掘金社区的相关技术文章。

属性说明类型默认值
属性说明类型默认值
worker用于加载和解析 PDF 数据的工作器PDFWorker-
withCredentials指示是否应使用 cookie 或授权标头等凭据发出跨站点访问控制请求。Booleanfalse
verbosity控制日志记录级别;应该使用 VerbosityLevel 中的常量Number-
useWorkerFetch在读取 CMap 和标准字体文件时启用在工作线程中使用 Fetch API。当为“true”时,会忽略“CMapReaderFactory”和“StandardFontDataFactory”选项。 Web 环境中的默认值为 true,Node.js 中的默认值为 falseBoolean-
useSystemFonts是否使用系统字体Booleanfalse
urlPDF的url地址String  URL
stopAtErrors拒绝某些方法,例如getOperatorList、getTextContent 和 RenderTask,当相关的 PDF 数据无法成功解析时,而不是尝试恢复任何可能的数据Booleanfalse
standardFontDataUrl标准字体文件所在的地址。包括尾部斜杠String-
StandardFontDataFactory读取标准字体文件时将使用的工厂。提供自定义工厂对于没有 Fetch API 或 XMLHttpRequest 支持的环境很有用,例如 Node.jsObject{DOMStandardFontDataFactory}
rangeChunkSize指定每个范围请求获取的最大字节数NumberDEFAULT_RANGE_CHUNK_SIZE
range允许使用自定义范围PDFDataRangeTransport-
pdfBug启用用于调试 PDF.js 的特殊钩子(请参阅web / debugger.js)Objectfalse
password用于解密受密码保护的 PDFString-
ownerDocument指定一个显式的文档上下文来创建元素并将资源(例如字体)加载到其中HTMLDocument-
maxImageSize总像素中允许的最大图像大小,即宽 * 高。不会呈现高于此值的图像。使用 -1 表示没有限制,这也是默认值Number-
lengthPDF 文件长度。它用于进度报告和范围请求操作Number-
isEvalSupported确定我们是否可以将字符串评估为 JavaScript。主要用于提高字体渲染的性能,以及解析 PDF 函数时Booleantrue
initialData带有第一部分或全部 pdf 数据的类型化数组。由扩展使用,因为在切换到范围请求之前已经加载了一些数据。TypedArray-
httpHeaders基本身份验证请求头Object-
fontExtraProperties从工作线程导出解析的字体数据时,包括在渲染 PDF 文档期间未使用的其他属性,这对于调试目的(和向后兼容性)可能很有用,但请注意,它会导致内存使用量增加Booleanfalse
enableXfa渲染 Xfa 表格Booleanfalse
docBaseUrl文档的基本 URL,在尝试恢复注释和大纲项的有效绝对 URL 时使用,(错误地)仅指定了相对 URLstring-
disableStream禁用 PDF 文件数据的流式传输。默认情况下,PDF.js 尝试分块加载 PDF 文件Objectfalse
disableRange禁用 PDF 文件的范围请求加载。启用后,如果服务器支持部分内容请求,则 PDF 将分块获取Booleanfalse
disableFontFace默认情况下,字体会转换为 OpenType 字体并通过字体加载 API 或@font-face 规则加载。如果禁用,字体将使用内置字体渲染器渲染,该渲染器使用原始路径命令构建字形。Boolean-
disableAutoFetch禁用预取 PDF 文件数据。启用范围请求后,即使不需要显示当前页面,PDF.js 也会自动继续获取更多数据Objectfalse
data二进制 PDF 数据。使用类型化数组 (Uint8Array) 来提高内存使用率。如果 PDF 数据是 BASE64 编码的,请先使用 atob() 将其转换为二进制字符串。TypedArray、Array、String-
cMapUrl预定义 Adobe CMap 所在的 URL。包括尾部斜杠String-
CMapReaderFactory自定义工厂对于没有 Fetch API 或 XMLHttpRequest 支持的环境很有用,例如 Node.jsObject{DOMCMapReaderFactory}
cMapPacked指定 Adobe CMap 是否是二进制打包Boolean-

最后发现,可能是库本身的限制或其他原因,PDF.js 在文件大小超过 13MB 时才开始快速渲染。由于当时处理的文件体积约为 13MB,即使采用分片下载,仍存在一段较长的白屏时间。因此,最终方案改为将 PDF 转换为图片后再进行显示。

以上即为 PDF.js 文件预览技巧的完整总结,更多相关资料请关注本站其他文章。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章