You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

关键注意事项

  1. 坐标转换:PDF的坐标原点在左下角,而DOM的原点在左上角,必须反转Y轴后再乘以缩放比例。
  2. 交互不冲突:给高亮元素设置pointerEvents: none,避免遮挡PDF的文本选择、滚动等交互。
  3. 动态高亮:可以通过监听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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 08:57:52