指南 · 进阶
版本基线 6.0.x。把 PDF.js 用进真实项目:文本层(可选中/可搜索)、注解层(链接/表单)、用户本地文件、远程加载与凭据、导出图片、HiDPI 完整处理、加载进度条。
一、文本层:让文字可选中/可搜索
canvas 只画位图,文字不能选。要可选中/可搜索,需把 getTextContent 的文本片段用 TextLayer 类叠成透明 <span>,且与 canvas 共用同一个 viewport:
// HTML:textLayer 容器叠在 canvas 之上,绝对定位、同尺寸
const textLayer = new pdfjsLib.TextLayer({
textContentSource: await page.getTextContent(),
container: textLayerDiv,
viewport,
});
await textLayer.render();旧的函数式
renderTextLayer(...)已弃用,新版用TextLayer类。文本层与 canvas 必须用一致的 scale/rotation,否则文字与图错位。
二、整本搜索关键字并高亮(思路)
async function searchAll(pdf: any, keyword: string) {
const hits: { page: number; index: number }[] = [];
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const tc = await page.getTextContent();
const text = tc.items.map((it: any) => it.str).join("");
let idx = text.indexOf(keyword);
while (idx !== -1) {
hits.push({ page: i, index: idx });
idx = text.indexOf(keyword, idx + keyword.length);
}
}
return hits;
}核心是基于文本层 + getTextContent 的文本/坐标,而不是对 canvas 像素做 OCR,也不是在 PDF 二进制里正则。
三、注解层:链接与表单
链接、表单控件、批注属于注解,需用 AnnotationLayer 类叠在 canvas 之上:
const annotationLayer = new pdfjsLib.AnnotationLayer({
div: annotationLayerDiv,
viewport,
page,
});
await annotationLayer.render({
annotations: await page.getAnnotations(),
viewport,
});链接可点、表单可填都靠注解层 DOM。它独立于 canvas 位图层与文本层,三层从下到上叠放。
四、用户本地文件(input/拖拽)
浏览器没有文件系统,把 File 读成二进制再用 data:
input.addEventListener("change", async () => {
const file = input.files![0];
const buf = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: buf }).promise;
// ... 渲染
});不能直接
getDocument({ url: file });也别用FileReader.readAsText(会破坏二进制),要arrayBuffer()。
五、远程加载与凭据
const pdf = await pdfjsLib.getDocument({
url: "https://api.example.com/secure.pdf",
withCredentials: true, // 带 Cookie/凭据
httpHeaders: { Authorization: "Bearer xxx" }, // 自定义头
}).promise;跨域报错 = 服务端没配 CORS
PDF.js 经 fetch/XHR 拉取,受同源策略约束。跨域失败通常是目标服务器未返回 Access-Control-Allow-Origin,需服务端放行,PDF.js 端无法绕过。
六、导出某页为图片(PNG)
PDF.js 渲染目标是 canvas,导图借助 canvas 原生 API:
await page.render({ canvas, viewport }).promise;
// 转 DataURL
const dataUrl = canvas.toDataURL("image/png");
// 或转 Blob 下载
canvas.toBlob((blob) => {
const a = document.createElement("a");
a.href = URL.createObjectURL(blob!);
a.download = "page-1.png";
a.click();
}, "image/png");没有
page.toPNG()这类一键方法,导图统一走「render 到 canvas → canvas API」。
七、HiDPI 完整处理
const viewport = page.getViewport({ scale: 1.5 });
const outputScale = window.devicePixelRatio || 1;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
const transform =
outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
await page.render({ canvas, viewport, transform }).promise;思路:物理像素按
devicePixelRatio放大、CSS 尺寸保持视口大小、render 传对应transform。这正是官方 helloworld 的做法。
八、加载进度条
const loadingTask = pdfjsLib.getDocument({ url });
loadingTask.onProgress = ({ loaded, total }) => {
if (total) console.log(`${Math.round((loaded / total) * 100)}%`);
};
const pdf = await loadingTask.promise;进度回调挂在 loadingTask 上,不在文档对象上——这也是
getDocument返回任务而非直接 Promise 的价值。
进入 指南 · 专家:CJK/字体资源(cMapUrl)、大文档虚拟化、Node 端抽文本、legacy 构建、打包器 worker 配置、与 jsPDF/pdf-lib 的选型。