如何在React中用pdfjs-dist动态缩放PDF并精准绘制多边形
PDFJS渲染适配容器与多边形坐标对齐问题
问题描述
在React组件中使用pdfjs-dist渲染尺寸不固定的PDF(如1800x1200、580x250等),需求:
- 画布动态缩放适配容器
- 基于0-1范围的相对坐标绘制多边形,确保与PDF指定位置对齐
当前实现中PDF渲染正常,但画布尺寸与多边形位置错位,例如原1800x1200的PDF视口显示为580x250时,多边形无法对齐。
问题分析与解决方案
1. 确保画布尺寸匹配PDF并正确缩放适配容器
当前代码核心问题:
- 依赖异步更新的
viewportCoords获取容器尺寸,初始值为0会导致计算错误 - 手动设置
transform进行缩放,不如直接用PDFJS的视口API更可靠
修改步骤:
- 直接获取容器DOM的实际尺寸,避免依赖state
- 基于PDF原始尺寸(scale=1时的视口)计算适配容器的缩放比例
- 使用计算后的缩放比例创建适配视口,画布宽高直接使用该视口的尺寸
2. 确保多边形与PDF坐标完美对齐
错位原因:
- PDF坐标系以左下角为原点,Canvas以左上角为原点,Y轴反转逻辑错误
- 依赖state中的
scale值,异步更新可能导致绘制时使用旧值 - 手动计算坐标易出错,未利用PDFJS提供的坐标转换方法
修改步骤:
- 使用PDF视口的
convertToViewportPoint方法转换相对坐标到PDF视口坐标 - 直接传递计算好的
scale给绘制函数,避免state异步问题 - 正确反转Y轴:Canvas的Y坐标 = 画布高度 - 视口转换后的Y坐标
修改后的完整代码片段
import { useState, useEffect, useRef } from 'react'; import { Box } from '@mui/material'; import * as pdfjsLib from 'pdfjs-dist'; import { GlobalWorkerOptions } from 'pdfjs-dist'; const PDFViewer = ({ pdfView }: { pdfView: File | null }) => { const canvasRef = useRef<HTMLCanvasElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null); const polygonDummy = [ { X: 0.34002524614334106, Y: 0.27618247270584106 }, { X: 0.8136482834815979, Y: 0.2764469087123871 }, { X: 0.8136501908302307, Y: 0.2848358154296875 }, { X: 0.34002646803855896, Y: 0.2845710515975952 }, ]; useEffect(() => { if (!pdfView || !canvasRef.current || !containerRef.current) return; const url = URL.createObjectURL(pdfView); const canvas = canvasRef.current; const container = containerRef.current; GlobalWorkerOptions.workerSrc = '../../node_modules/pdfjs-dist/build/pdf.worker.mjs'; const loadingTask = pdfjsLib.getDocument(url); loadingTask.promise .then(async (pdf) => { const page = await pdf.getPage(1); // 获取PDF原始尺寸(scale=1时的视口) const originalViewport = page.getViewport({ scale: 1 }); // 获取容器实际尺寸 const containerWidth = container.clientWidth; const containerHeight = container.clientHeight; // 计算适配容器的缩放比例,保持宽高比 const scaleX = containerWidth / originalViewport.width; const scaleY = containerHeight / originalViewport.height; const scale = Math.min(scaleX, scaleY); // 创建适配后的视口 const scaledViewport = page.getViewport({ scale }); // 设置画布尺寸 canvas.width = Math.floor(scaledViewport.width); canvas.height = Math.floor(scaledViewport.height); canvas.style.width = `${scaledViewport.width}px`; canvas.style.height = `${scaledViewport.height}px`; const context = canvas.getContext('2d'); if (context) { // 使用适配后的视口渲染PDF,无需手动设置transform const renderContext = { canvasContext: context, viewport: scaledViewport, }; await page.render(renderContext).promise; // 传递计算好的参数给绘制函数 drawPolygon(context, scaledViewport, polygonDummy); } }) .catch((error) => console.error('加载PDF错误:', error)); return () => URL.revokeObjectURL(url); }, [pdfView]); const drawPolygon = ( context: CanvasRenderingContext2D, viewport: pdfjsLib.PageViewport, polygon: { X: number; Y: number }[] ) => { context.beginPath(); context.strokeStyle = 'red'; context.lineWidth = 2; const canvasHeight = viewport.height; // 转换相对坐标到Canvas像素坐标 const coords = polygon.map((point) => { // 相对坐标转PDF视口坐标(0-1范围映射到PDF原始宽高) const pdfPoint = viewport.convertToViewportPoint(point.X * viewport.width / viewport.scale, point.Y * viewport.height / viewport.scale); // 转换为Canvas坐标:反转Y轴 return { x: pdfPoint.x, y: canvasHeight - pdfPoint.y, }; }); if (coords.length > 0) { context.moveTo(coords[0].x, coords[0].y); for (let i = 1; i < coords.length; i++) { context.lineTo(coords[i].x, coords[i].y); } context.closePath(); context.stroke(); } }; return ( <Box ref={containerRef} sx={{ width: '100%', // 容器占满父容器宽度,或设置固定值 height: '600px', // 可根据需求调整高度 overflow: 'auto', border: '1px solid black', }} > <canvas ref={canvasRef}></canvas> </Box> ); }; export default PDFViewer;
关键修改点说明
- 新增
containerRef直接获取容器DOM尺寸,规避state异步更新的问题 - 使用
originalViewport(scale=1)计算缩放比例,确保基于PDF真实尺寸适配容器 - 用
scaledViewport渲染PDF,自动处理缩放逻辑,无需手动设置transform - 绘制多边形时,借助PDFJS的坐标转换方法,配合Y轴反转得到正确的Canvas坐标
- 绘制函数直接使用当前计算的视口参数,不依赖state,避免异步更新导致的错位
内容的提问来源于stack exchange,提问作者lol
相关产品推荐
相关产品推荐

