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

react-pdf页面上层自定义Canvas缩放异常问题求助

React-PDF 缩放时Canvas绘图像素化/线条变粗问题解决

问题描述

我正在基于react-pdf渲染的页面构建PDF编辑器,没有使用Page组件暴露的canvasRef,而是单独创建了一个Canvas元素覆盖在react-pdf的Page上方。Page所在的父div会将页面缩放到超过A4初始尺寸(596×842px),并显示滚动条允许用户滚动。当前Canvas元素已定位在Page上方,但缩放操作后绘图出现异常:在596×842及更小尺寸下绘图正常,一旦页面缩放超过该尺寸,绘图就会像素化,线条也变粗。此问题并非react-pdf仓库的bug,需要解决缩放适配相关逻辑。

现有代码片段

组件渲染代码

{pdfFile && (
  <div id="row" style={{ marginTop: '60px', width: "100vw", maxWidth: '1024px', display: "flex", overflowY: "auto" }}>
    <StyledEditorHolder id="row">
      <PdfContent ref={pdfWrapperRef}>
        <StyledPdfDocument file={pdfFile} onLoadSuccess={handleDocumentLoadSuccess}>
          <StyledPdfPage pageNumber={currentPage} width={wrapperDivSize} />
        </StyledPdfDocument>
        <CanvasContainer
          ref={canvasRef}
          onPointerDown={handleCanvasTouchStart} 
          onPointerUp={handleCanvasTouchEnd}   
          onPointerMove={handleCanvasTouchMove}
          blockScroll={drawingEnabled}
        />
      </PdfContent>
    </StyledEditorHolder> 
  </div>
)}

核心逻辑代码

useEffect(() => {
  if(pdfWrapperRef.current) {
    setWrapperDivSize(pdfWrapperRef.current.getBoundingClientRect().width);
    const handleResize = throttle(() => {
      if (pdfWrapperRef.current) {
        const pdfPage = document.querySelector('.react-pdf__Page');
        const newWidth = pdfPage.getBoundingClientRect().width;
        setWrapperDivSize(pdfWrapperRef.current.getBoundingClientRect().width);
      }
    }, 500);
    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }
}, [isOpen, pdfWrapperRef.current]);

const handleDocumentLoadSuccess = ({ numPages }) => {
  setNumPages(numPages);
  setCurrentPage(1);
  setDrawnPaths(Array.from({ length: numPages }, () => []));

  const pdfPage = document.querySelector('.react-pdf__Page'); 
  if (pdfPage) {
    const rect = pdfPage.getBoundingClientRect();
    console.log("Initial width", rect.width)
    setCurrentWidth(prevState => rect.width);
    canvasRef.current.width = 596;
    canvasRef.current.height = 842;

    console.log("Canvas", rect.width, rect.height)

    // 计算初始缩放比例
    const scaleFactor = rect.width / 595.276;
    setScaleFactor(scaleFactor);
    console.log("Initial Scale factor", scaleFactor)

    setIsOpen(true);
  }
};

样式代码

export const StyledPdfEditor = styled.div`
  display: flex;
  flex-direction: column;
  flex: 1;
  align-items: center;
`;

export const StyledEditorHolder = styled.div`
  box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px;
  width: 100%;
  border-radius: 20px;
  display: flex;
  position: relative;
  justify-content: center;
  overflow: hidden;
`

export const PdfContent = styled.div<CanvasProps>`
  position: relative;
  display: flex;
  max-width: 1024px;
  touch-action: ${props => props.blockScroll ? "none" : "auto"};
  justify-content: center;
  overflow: auto;
  width: 100vw;
`;

export const CanvasContainer = styled.canvas<CanvasProps>`
  position: absolute;
  width: 100%;
  height: auto;
  touch-action: ${props => props.blockScroll ? "none" : "auto"};
  z-index: 100;
`;

export const StyledPdfPage = styled(Page)`
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;

  .react-pdf__Page {
    display: flex;
    justify-content: center;
    align-items: center;
    align-self: center;
  }
`;

export const StyledPdfDocument = styled(Document)`
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;

  .react-pdf__Document {
    width: 100%;
  }
`;

解决方案

1. 分离Canvas的实际像素尺寸与显示尺寸

Canvas的width/height属性是绘图的实际像素分辨率,CSS的width/height是显示尺寸。当前固定Canvas为596×842,缩放时CSS拉伸导致像素化,需根据页面缩放比例动态设置Canvas实际尺寸:

修改handleDocumentLoadSuccess:

const handleDocumentLoadSuccess = ({ numPages }) => {
  setNumPages(numPages);
  setCurrentPage(1);
  setDrawnPaths(Array.from({ length: numPages }, () => []));

  const pdfPage = document.querySelector('.react-pdf__Page'); 
  if (pdfPage) {
    const rect = pdfPage.getBoundingClientRect();
    const scaleFactor = rect.width / 595.276; // A4标准宽度(pt转px)
    // 计算Canvas实际像素尺寸,取整避免模糊
    const canvasWidth = Math.round(596 * scaleFactor);
    const canvasHeight = Math.round(842 * scaleFactor);
    canvasRef.current.width = canvasWidth;
    canvasRef.current.height = canvasHeight;
    
    setCurrentWidth(rect.width);
    setScaleFactor(scaleFactor);
    setIsOpen(true);
  }
};

修改resize监听的handleResize:

const handleResize = throttle(() => {
  if (pdfWrapperRef.current && canvasRef.current) {
    const pdfPage = document.querySelector('.react-pdf__Page');
    if (pdfPage) {
      const rect = pdfPage.getBoundingClientRect();
      const scaleFactor = rect.width / 595.276;
      const canvasWidth = Math.round(596 * scaleFactor);
      const canvasHeight = Math.round(842 * scaleFactor);
      
      canvasRef.current.width = canvasWidth;
      canvasRef.current.height = canvasHeight;
      setScaleFactor(scaleFactor);
      setWrapperDivSize(rect.width);
    }
  }
}, 500);

2. 绘图坐标转换

用户的鼠标/触摸坐标基于显示尺寸,需转换为Canvas实际像素坐标,避免线条变粗或位置偏移:

// 以handleCanvasTouchMove为例
const handleCanvasTouchMove = (e) => {
  if (!drawingEnabled) return;
  const rect = canvasRef.current.getBoundingClientRect();
  // 将客户端坐标转换为Canvas实际像素坐标
  const x = (e.clientX - rect.left) / scaleFactor;
  const y = (e.clientY - rect.top) / scaleFactor;
  // 使用转换后的x、y执行绘图逻辑
  // ...你的绘图代码
};

3. 修正Canvas CSS样式

确保Canvas与Page尺寸完全匹配,避免比例偏差:

export const CanvasContainer = styled.canvas<CanvasProps>`
  position: absolute;
  width: 100%;
  height: 100%; /* 替换auto为100%,与Page高度完全匹配 */
  touch-action: ${props => props.blockScroll ? "none" : "auto"};
  z-index: 100;
  image-rendering: crisp-edges; /* 可选,优化缩放后绘图清晰度 */
`;

4. 同步Page与Canvas的缩放状态

确保wrapperDivSize始终对应Page实际宽度,react-pdf的Page组件width属性绑定该值,保证两者缩放比例一致。


内容的提问来源于stack exchange,提问作者imim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 01:00:58