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

如何在HTML中实现可跨页拖拽的PDF签章并显示坐标?

问题:实现带坐标显示的可跨页拖拽签章区域

我需要创建一个用于标记签名区域的签章,要求添加能显示X、Y坐标的HTML元素,且该元素可在PDF页面间拖拽。请问是否有人已实现该方案?我尝试使用ngx-extended-pdf-viewer组件,但无法渲染HTML元素或获取元素位置,相关代码如下:

public async addTextEditorForPage(pageNumber: number): Promise<void> {
    const pageSize = await this.getPageSize(pageNumber);
    console.log(`Page ${pageNumber} - Orientation: ${pageSize.orientation}`);

    const lines = await this.pdfService.getPageAsLines(pageNumber);
    const firstLineHeight = lines.length > 0 ? lines[0].height : 0;

    const rectWidth = pageSize.orientation === 'landscape' ? lines[lines.length - 1].width - 20 : firstLineHeight;

    const textEditorAnnotation: FreeTextEditorAnnotation = {
      annotationType: 3,
      color: [0, 0, 0],
      fontSize: 13,
      value: `ABCABCABC TEST`,
      pageIndex: pageNumber - 1, // 页面索引从0开始
      rect: [
        100, // 文本与左侧边框的间距
        0,
        0,
        rectWidth,
      ],
      rotation: 0,
    };

    this.addedAnnotations.push(textEditorAnnotation);
    this.pdfService.addEditorAnnotation(textEditorAnnotation);
}

实现方案

核心思路

ngx-extended-pdf-viewer支持在PDF页面容器上层叠加自定义HTML元素,结合拖拽事件监听和PDF坐标转换,就能实现带坐标显示、可跨页拖拽的签章区域:

  • HTML元素嵌入:通过页面渲染事件获取对应PDF页面的DOM容器,动态创建签章元素并挂载
  • 坐标转换:利用PDF页面的getViewport()方法实现屏幕坐标与PDF内部坐标的双向转换
  • 跨页拖拽:拖拽过程中检测鼠标位置,触发翻页后重新定位签章元素到新页面

问题修正说明

你当前使用的FreeTextEditorAnnotation是PDF原生注解,无法渲染自定义HTML内容。要实现带坐标显示的签章,必须单独创建HTML节点,而非依赖PDF注解。

完整代码示例

// 监听PDF页面渲染完成事件
this.pdfService.onPageRendered().subscribe(async (page) => {
  const pageNumber = page.pageNumber;
  const viewport = page.getViewport({ scale: this.pdfService.getZoom() });
  
  // 创建带坐标显示的签章元素
  const signatureMarker = document.createElement('div');
  signatureMarker.style.position = 'absolute';
  signatureMarker.style.width = '120px';
  signatureMarker.style.height = '60px';
  signatureMarker.style.border = '2px dashed #d9534f';
  signatureMarker.style.backgroundColor = 'rgba(217, 83, 79, 0.1)';
  signatureMarker.style.cursor = 'move';
  signatureMarker.style.display = 'flex';
  signatureMarker.style.alignItems = 'center';
  signatureMarker.style.justifyContent = 'center';
  signatureMarker.style.fontSize = '12px';
  signatureMarker.style.color = '#333';
  
  // 将元素挂载到当前页面的容器
  const pageContainer = document.querySelector(`.page[data-page-number="${pageNumber}"]`);
  if (pageContainer) {
    pageContainer.appendChild(signatureMarker);
    
    // 初始化位置(PDF坐标:X=100, Y=页面高度-150,Y轴向上)
    const initialPdfX = 100;
    const initialPdfY = viewport.height - 150;
    const screenX = initialPdfX * viewport.scale;
    const screenY = (viewport.height - initialPdfY) * viewport.scale;
    
    signatureMarker.style.left = `${screenX}px`;
    signatureMarker.style.top = `${screenY}px`;
    signatureMarker.textContent = `X: ${initialPdfX}, Y: ${initialPdfY}`;
    
    // 绑定拖拽逻辑
    this.setupDragHandler(signatureMarker, viewport, pageNumber);
  }
});

// 拖拽事件处理函数
private setupDragHandler(element: HTMLElement, viewport: any, currentPage: number) {
  let isDragging = false;
  let startClientX: number, startClientY: number;
  let initialLeft: number, initialTop: number;
  
  // 开始拖拽
  element.addEventListener('mousedown', (e) => {
    isDragging = true;
    startClientX = e.clientX;
    startClientY = e.clientY;
    initialLeft = parseInt(element.style.left) || 0;
    initialTop = parseInt(element.style.top) || 0;
    element.style.zIndex = '9999'; // 拖拽时置于顶层
    element.style.opacity = '0.8';
  });
  
  // 拖拽过程
  document.addEventListener('mousemove', async (e) => {
    if (!isDragging) return;
    
    const deltaX = e.clientX - startClientX;
    const deltaY = e.clientY - startClientY;
    const newLeft = initialLeft + deltaX;
    const newTop = initialTop + deltaY;
    
    // 转换为PDF内部坐标(Y轴方向反转)
    const pdfX = Math.round(newLeft / viewport.scale);
    const pdfY = Math.round(viewport.height - (newTop / viewport.scale));
    
    // 更新元素位置和坐标显示
    element.style.left = `${newLeft}px`;
    element.style.top = `${newTop}px`;
    element.textContent = `X: ${pdfX}, Y: ${pdfY}`;
    
    // 检测翻页触发条件(距离容器边缘50px时触发)
    const containerRect = document.querySelector('.ngx-extended-pdf-viewer-container')?.getBoundingClientRect();
    if (!containerRect) return;
    
    const totalPages = await this.pdfService.getNumberOfPages();
    // 向上拖拽触顶,翻上一页
    if (e.clientY < containerRect.top + 50 && currentPage > 1) {
      this.pdfService.goToPage(currentPage - 1);
      this.repositionMarker(currentPage - 1, pdfX, pdfY);
      currentPage -= 1;
    }
    // 向下拖拽触底,翻下一页
    else if (e.clientY > containerRect.bottom - 50 && currentPage < totalPages) {
      this.pdfService.goToPage(currentPage + 1);
      this.repositionMarker(currentPage + 1, pdfX, pdfY);
      currentPage += 1;
    }
  });
  
  // 结束拖拽
  document.addEventListener('mouseup', () => {
    isDragging = false;
    element.style.zIndex = 'auto';
    element.style.opacity = '1';
  });
}

// 翻页后重新定位签章元素
private async repositionMarker(pageNumber: number, pdfX: number, pdfY: number) {
  // 等待页面渲染完成
  await new Promise(resolve => setTimeout(resolve, 300));
  
  const page = await this.pdfService.getPage(pageNumber);
  const viewport = page.getViewport({ scale: this.pdfService.getZoom() });
  const pageContainer = document.querySelector(`.page[data-page-number="${pageNumber}"]`);
  
  if (!pageContainer) return;
  
  // 移除旧页面的签章元素(可选,根据需求决定是否保留多页面签章)
  const oldMarker = document.querySelector(`.page:not([data-page-number="${pageNumber}"]) div[style*="dashed #d9534f"]`);
  oldMarker?.remove();
  
  // 创建新页面的签章元素
  const newMarker = document.createElement('div');
  newMarker.style.position = 'absolute';
  newMarker.style.width = '120px';
  newMarker.style.height = '60px';
  newMarker.style.border = '2px dashed #d9534f';
  newMarker.style.backgroundColor = 'rgba(217, 83, 79, 0.1)';
  newMarker.style.cursor = 'move';
  newMarker.style.display = 'flex';
  newMarker.style.alignItems = 'center';
  newMarker.style.justifyContent = 'center';
  newMarker.style.fontSize = '12px';
  newMarker.style.color = '#333';
  
  const screenX = pdfX * viewport.scale;
  const screenY = (viewport.height - pdfY) * viewport.scale;
  newMarker.style.left = `${screenX}px`;
  newMarker.style.top = `${screenY}px`;
  newMarker.textContent = `X: ${pdfX}, Y: ${pdfY}`;
  
  pageContainer.appendChild(newMarker);
  this.setupDragHandler(newMarker, viewport, pageNumber);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 02:07:52