PSPDFKit自定义注解渲染后丢失文本选择功能的解决咨询
PSPDFKit注解区域文本选择失效问题的解决方案
问题背景
我们在React项目中用PSPDFKit做PDF阅读器,给TextAnnotation、MarkupAnnotation、LinkAnnotation、HighlightAnnotation、UnderlineAnnotation这几类注解加了自定义逻辑:鼠标停留在注解上,停止移动后弹出带指定信息的弹窗。现在弹窗功能正常,但在已有注解的区域里,PSPDFKit原生的文本选择功能用不了了,推测是自定义的DOM节点和事件监听导致的。
问题根源
- 自定义div节点设置了
pointer-events: all;,完全覆盖在注解区域上方,拦截了原本应该传递给PSPDFKit的文本选择相关事件。 mouseoverListener和mouseLeaveListener里调用了e.preventDefault()和e.stopPropagation(),阻止了事件向PSPDFKit原生组件传递,导致文本选择的触发逻辑被中断。- 弹窗触发的定时器逻辑没有区分“单纯停留”和“拖拽选择文本”两种操作,误把文本选择的拖拽动作当成了停留,干扰了原生功能。
解决方案与优化思路
1. 动态控制pointer-events,减少事件拦截
把自定义div的默认pointer-events设为none,只在需要监听鼠标停留的时候临时开启,弹窗触发后再关闭,让文本选择事件能正常透传。
2. 移除不必要的事件阻止操作
去掉mouseoverListener和mouseLeaveListener里的e.preventDefault()和e.stopPropagation(),让事件正常传递给PSPDFKit的原生组件,保证文本选择事件能被触发。
3. 区分鼠标停留和文本选择操作
增加拖拽状态检测,当用户按下鼠标左键(文本选择的起始动作)时,取消弹窗定时器,避免干扰原生文本选择流程。
4. 优化事件监听的生命周期
在注解销毁时清理所有自定义事件监听,避免内存泄漏和残留的事件拦截。
修改后的完整代码示例
customRenderers: { Annotation: ({annotation}) =>{ if ( annotation instanceof PSPDFKit.Annotations.TextAnnotation || annotation instanceof PSPDFKit.Annotations.MarkupAnnotation || annotation instanceof PSPDFKit.Annotations.LinkAnnotation || annotation instanceof PSPDFKit.Annotations.HighlightAnnotation || annotation instanceof PSPDFKit.Annotations.UnderlineAnnotation ) { const node = instance.contentDocument.createElement("div"); node.classList.add('myAnnotationCustom') node.dataset.annotationId = annotation.id; // 默认关闭pointer-events,允许事件透传 node.style.cssText = "position: absolute; width: 100%; height: 100%;pointer-events: none;"; node.innerHTML = `<div data-title="${annotation.id}"></div>`; let isDragging = false; const mouseoverListener = async function (e) { if(openedAnnotationId.current !== null && openedAnnotationId.current !== annotation.id) return if(isPopupOpened.current) return if (annotation) { mouseStopActionHasAnnotation.current = true; // 临时开启pointer-events以监听鼠标动作 node.style.pointerEvents = 'all'; } }; const mouseLeaveListener = (e) => { if(isPopupOpened.current) return mouseStopActionHasAnnotation.current = false; // 恢复pointer-events,允许文本选择 node.style.pointerEvents = 'none'; } const handleOpenPopup = () => { setCurrentAnnotation(annotation); openedAnnotationId.current = annotation.id; isPopupOpened.current = true; setShowPopup(true); // 弹窗开启后关闭pointer-events,避免拦截后续操作 node.style.pointerEvents = 'none'; } const handleCancelPopup = () => { if(!isPopupOpened.current) { setCurrentAnnotation(null); } } const handleMouseMove = (event) => { if(isPopupOpened.current || isDragging) return clearTimeout(timer); timer = setTimeout(() => { if(annotation && mouseStopActionHasAnnotation.current) { const mousePosition = { event: event, x: event.pageX - 80, y: event.pageY - 120 } setMousePosition({ top: mousePosition.y, left: mousePosition.x }) handleOpenPopup() } else { handleCancelPopup(); } node.removeEventListener("mousemove", handleMouseMove); }, 500); } // 监听鼠标左键按下,标记拖拽状态 const handleMouseDown = (e) => { if (e.button === 0) { isDragging = true; clearTimeout(timer); mouseStopActionHasAnnotation.current = false; } } // 监听鼠标左键抬起,重置拖拽状态 const handleMouseUp = () => { isDragging = false; } node.addEventListener("mouseenter", mouseoverListener, {once: true}); node.addEventListener("mouseleave", mouseLeaveListener, {once: true}); node.addEventListener('mousemove', handleMouseMove); node.addEventListener('mousedown', handleMouseDown); node.addEventListener('mouseup', handleMouseUp); // 注解销毁时清理事件监听 const cleanupListener = (event) => { if (event.annotation.id === annotation.id) { node.removeEventListener("mouseenter", mouseoverListener); node.removeEventListener("mouseleave", mouseLeaveListener); node.removeEventListener('mousemove', handleMouseMove); node.removeEventListener('mousedown', handleMouseDown); node.removeEventListener('mouseup', handleMouseUp); instance.removeEventListener('annotationDestroyed', cleanupListener); } } instance.addEventListener('annotationDestroyed', cleanupListener); return { node, append: true, }; } } }
内容的提问来源于stack exchange,提问作者Antoni0Zac
相关产品推荐
相关产品推荐

