React JS项目中基于PDF JS Canvas实现文档区域高亮的技术咨询
React中PDF.js文档透明高亮的实现方案
可行性说明
完全可以实现,PDF.js提供两种核心实现思路:叠加DOM元素(灵活易维护,适合动态高亮)或Canvas直接绘制(与PDF渲染融为一体,无层级问题),以下是具体实现示例。
方法一:叠加DOM元素实现动态高亮
这种方式通过在PDF渲染的Canvas上层添加绝对定位的半透明DOM元素,适合需要动态添加/删除高亮的场景。
核心代码示例
import { useState, useEffect, useRef } from 'react'; import pdfjsLib from 'pdfjs-dist'; import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry'; // 配置PDF.js Worker pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker; const PDFViewerWithHighlight = () => { const [pdfDoc, setPdfDoc] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [isRendering, setIsRendering] = useState(false); const canvasRef = useRef(null); const viewerContainerRef = useRef(null); // 存储高亮区域:格式为 { pageNumber, x, y, width, height }(PDF文档原生坐标) const [highlights, setHighlights] = useState([ // 示例高亮区域,可替换为用户交互生成的坐标 { pageNumber: 1, x: 60, y: 120, width: 220, height: 35 } ]); const SCALE = 1.5; // PDF渲染缩放比例 // 加载PDF文档 useEffect(() => { const loadPDF = async () => { const pdf = await pdfjsLib.getDocument('/sample.pdf').promise; setPdfDoc(pdf); }; loadPDF(); }, []); // 渲染指定页码 const renderPage = async (pageNum) => { setIsRendering(true); const page = await pdfDoc.getPage(pageNum); const viewport = page.getViewport({ scale: SCALE }); const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); // 设置Canvas尺寸 canvas.width = viewport.width; canvas.height = viewport.height; // 渲染PDF内容 await page.render({ canvasContext: ctx, viewport }).promise; setIsRendering(false); }; // 页码切换时重新渲染 useEffect(() => { if (pdfDoc) renderPage(currentPage); }, [pdfDoc, currentPage]); // 转换PDF原生坐标为DOM页面坐标 const getHighlightStyle = (highlight) => { if (!pdfDoc) return {}; const viewport = pdfDoc.getPage(highlight.pageNumber).then(page => page.getViewport({ scale: SCALE })); // PDF原点在左下角,DOM原点在左上角,需反转Y轴 const domY = viewport.height - highlight.y - highlight.height; return { position: 'absolute', left: `${highlight.x * SCALE}px`, top: `${domY * SCALE}px`, width: `${highlight.width * SCALE}px`, height: `${highlight.height * SCALE}px`, backgroundColor: 'rgba(255, 255, 0, 0.3)', // 半透明黄色高亮 pointerEvents: 'none', // 不干扰PDF的文本选择/交互 borderRadius: '2px' // 可选:添加圆角优化视觉 }; }; return ( <div ref={viewerContainerRef} style={{ position: 'relative', maxWidth: '800px', margin: '0 auto' }}> {/* PDF渲染Canvas */} <canvas ref={canvasRef} /> {/* 渲染当前页的高亮元素 */} {highlights .filter(h => h.pageNumber === currentPage) .map((highlight, idx) => ( <div key={`highlight-${idx}`} style={getHighlightStyle(highlight)} /> ))} {/* 分页控制 */} <div style={{ marginTop: '16px', display: 'flex', gap: '8px' }}> <button onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))} disabled={currentPage === 1 || isRendering} > 上一页 </button> <button onClick={() => setCurrentPage(prev => Math.min(pdfDoc?.numPages || 1, prev + 1))} disabled={currentPage === pdfDoc?.numPages || isRendering} > 下一页 </button> </div> </div> ); }; export default PDFViewerWithHighlight;
关键注意事项
- 坐标转换:PDF的坐标原点在左下角,而DOM的原点在左上角,必须反转Y轴后再乘以缩放比例。
- 交互不冲突:给高亮元素设置
pointerEvents: none,避免遮挡PDF的文本选择、滚动等交互。 - 动态高亮:可以通过监听Canvas的
mousedown/mouseup事件,获取鼠标位置并转换为PDF原生坐标,添加到highlights数组中实现动态标记。
方法二:Canvas直接绘制高亮
如果不需要动态修改高亮,或希望高亮与PDF渲染融为一体,可以在PDF渲染完成后直接在Canvas上绘制半透明矩形。
核心修改点(在renderPage函数中添加)
// 渲染PDF后绘制高亮 const viewport = page.getViewport({ scale: SCALE }); const ctx = canvas.getContext('2d'); ctx.fillStyle = 'rgba(255, 255, 0, 0.3)'; // 遍历当前页的高亮区域 highlights.filter(h => h.pageNumber === pageNum).forEach(highlight => { const domY = viewport.height - highlight.y - highlight.height; ctx.fillRect( highlight.x * SCALE, domY * SCALE, highlight.width * SCALE, highlight.height * SCALE ); });
这种方法的优势是没有DOM层级问题,但修改高亮需要重新渲染整个页面,适合静态高亮场景。
第三方库简化实现
如果使用react-pdf等封装好的React PDF库,可以通过其提供的Page组件的renderAnnotation或自定义render属性快速实现高亮,核心逻辑与上述两种方法一致。
内容的提问来源于stack exchange,提问作者Praveen Kumar
相关产品推荐
相关产品推荐

