如何在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
相关产品推荐
相关产品推荐

