
本文详解 PDF.js 在 React 中渲染 PDF 时“白屏无内容”的根本原因——误将 PDF 页面容器设为 元素,而 Canvas 的子元素在浏览器中被忽略;提供正确使用 div 容器的修复方案,并附可运行代码与关键注意事项。
本文详解 pdf.js 在 react 中渲染 pdf 时“白屏无内容”的根本原因——误将 pdf 页面容器设为 `canvas>` 元素,而 canvas 的子元素在浏览器中被忽略;提供正确使用 `div` 容器的修复方案,并附可运行代码与关键注意事项。
在基于 PDF.js 构建 React PDF 预览组件时,一个常见却极易被忽视的错误是:将动态创建的 PDF 页面(含 。这会导致页面完全空白——不是加载失败、不是报错,而是“静默失效”。
❌ 错误根源:Canvas 不支持子节点渲染
根据 HTML 规范,
在原始代码中:
return (
<div className={styles.cavasLayer}>
<canvas id="the-cavas" ref={canvasRef} /> {/* ❌ 错误:用 canvas 作容器 */}
</div>
);随后在 renderPdfPage 中执行:
canvasRef.current.appendChild(divPage); // ⚠️ divPage 及其内部 canvas 被丢弃!
这就是 PDF “消失”的根本原因——不是 PDF.js 没工作,而是渲染结果被塞进了“不可见的容器”。
✅ 正确解法:使用语义化容器(如 )应将承载 PDF 页面的 DOM 节点改为标准块级容器(推荐
),确保动态插入的页面 能被正常渲染:const Previewer = ({ pdfUrl }) => {
const pdfContainerRef = useRef(null); // ✅ 改用 ref 指向 div 容器
useEffect(() => {
if (pdfUrl) initPdf(pdfUrl);
}, [pdfUrl]);
const initPdf = (url) => {
const { pdfjsLib } = window;
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.worker.min.js";
return pdfjsLib.getDocument({
url,
cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/cmaps/",
cMapPacked: true,
}).promise.then((pdf) => {
const totalPages = pdf.numPages;
for (let i = 0; i < totalPages; i++) {
renderPdfPage(pdf, pdfjsLib, i + 1);
}
});
};
const renderPdfPage = (pdf, pdfjsLib, pageNum) => {
return pdf.getPage(pageNum).then((page) => {
const viewport = page.getViewport({ scale: 1.5 }); // 建议适当缩放提升可读性
const divPage = document.createElement("div");
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
// 设置 canvas 尺寸(必须!否则渲染为空白)
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = { canvasContext: context, viewport };
const renderTask = page.render(renderContext);
renderTask.promise.then(() => {
// 可选:添加文本图层(支持选择/复制文字)
page.getTextContent().then((textContent) => {
const textLayer = document.createElement("div");
textLayer.className = "textLayer";
textLayer.style.cssText = `
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
overflow: hidden; opacity: 0.2;
`;
divPage.appendChild(textLayer);
pdfjsLib.renderTextLayer({
textContentSource: textContent,
container: textLayer,
viewport,
textDivs: [],
});
});
});
divPage.appendChild(canvas);
pdfContainerRef.current?.appendChild(divPage); // ✅ 追加到 div 容器
});
};
return (
<div className="pdf-preview">
<div ref={pdfContainerRef} /> {/* ✅ 正确:div 作为容器 */}
{/* 注意:textLayer 由 JS 动态创建并注入,无需预先声明 */}
</div>
);
};⚠️ 关键注意事项
-
容器类型必须为非替换元素:仅 、
、 等常规容器有效;避免 、
、
应将承载 PDF 页面的 DOM 节点改为标准块级容器(推荐
const Previewer = ({ pdfUrl }) => {
const pdfContainerRef = useRef(null); // ✅ 改用 ref 指向 div 容器
useEffect(() => {
if (pdfUrl) initPdf(pdfUrl);
}, [pdfUrl]);
const initPdf = (url) => {
const { pdfjsLib } = window;
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.worker.min.js";
return pdfjsLib.getDocument({
url,
cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/cmaps/",
cMapPacked: true,
}).promise.then((pdf) => {
const totalPages = pdf.numPages;
for (let i = 0; i < totalPages; i++) {
renderPdfPage(pdf, pdfjsLib, i + 1);
}
});
};
const renderPdfPage = (pdf, pdfjsLib, pageNum) => {
return pdf.getPage(pageNum).then((page) => {
const viewport = page.getViewport({ scale: 1.5 }); // 建议适当缩放提升可读性
const divPage = document.createElement("div");
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
// 设置 canvas 尺寸(必须!否则渲染为空白)
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = { canvasContext: context, viewport };
const renderTask = page.render(renderContext);
renderTask.promise.then(() => {
// 可选:添加文本图层(支持选择/复制文字)
page.getTextContent().then((textContent) => {
const textLayer = document.createElement("div");
textLayer.className = "textLayer";
textLayer.style.cssText = `
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
overflow: hidden; opacity: 0.2;
`;
divPage.appendChild(textLayer);
pdfjsLib.renderTextLayer({
textContentSource: textContent,
container: textLayer,
viewport,
textDivs: [],
});
});
});
divPage.appendChild(canvas);
pdfContainerRef.current?.appendChild(divPage); // ✅ 追加到 div 容器
});
};
return (
<div className="pdf-preview">
<div ref={pdfContainerRef} /> {/* ✅ 正确:div 作为容器 */}
{/* 注意:textLayer 由 JS 动态创建并注入,无需预先声明 */}
</div>
);
};⚠️ 关键注意事项
-
容器类型必须为非替换元素:仅 、
、 等常规容器有效;避免 、 、










