使用pdfjs移步–
vue2使用pdfjs-dist实现pdf预览(iframe形式,不修改pdfjs原来的ui和控件,dom层可以用display去掉一部分组件)
方案1:获取选择文本内容的最前面的字符坐标的位置(这种写法会导致如果选择超出pdf容器的高度之后,导致按钮显示不出来,这种方法显示位置固定)
方案2:获取选择文本最后鼠标离开位置的坐标(目前没发现bug,这种方法显示的位置不固定)这个方法在最下面,核心位置计算方法handleTextSelectionPdf,updateToolPositionPdf
- 实现案例
- pdf容器创建,悬浮盒子创建
<iframe:src="pdfurl"class="pdfContent"ref="pdfViewer"frameborder="0"width="100%"height="850px"></iframe><divv-show="selectionToolsVisible"class="selection-tools":style="selectionPosition"@mousedown.prevent><div class="tool-item" @click.stop="getAihelper('entocn')"><img src="../../assets/detailImage/enToCn.png" alt="" /><span class="tool-text">翻译</span></div></div>
- data实例
data() {return {selectionToolsVisible: false,selectionPosition: { top: '0px', left: '0px' },selectionTimer: null,};},
- mounted注册鼠标事件-注册pdf的监听
this.$refs.pdfViewer.onload = () => {const iframeDoc =this.$refs.pdfViewer.contentDocument ||this.$refs.pdfViewer.contentWindow.document;iframeDoc.addEventListener('mouseup', this.handleTextSelectionPdf);};
- pdf监听代码-方案1
handleTextSelectionPdf(e) {clearTimeout(this.selectionTimer);// 缓存关键事件属性const targetElement = e?.target || document.activeElement;// const cachedSelection = window.getSelection().toString().trim();this.selectionTimer = setTimeout(() => {const selection =this.$refs.pdfViewer.contentWindow.getSelection();// 增强型六重验证const isValid =selection.rangeCount > 0 &&!selection.isCollapsed &&selection.toString().trim().length >= 1 && // 允许单字符选择targetElement.closest('#viewerContainer');if (isValid) {// console.log(selection);const range = selection.getRangeAt(0);const rect = this.getAdjustedRectPdf(range);this.updateToolPositionPdf(rect);this.selectionToolsVisible = true;this.tempSelection = selection.toString();} else {this.selectionToolsVisible = false;}}, 50); // 优化响应时间},getAdjustedRectPdf(range) {const tempSpan = document.createElement('span');range.insertNode(tempSpan);const rect = tempSpan.getBoundingClientRect();tempSpan.remove();// 获取 iframe 在整个页面中的位置const iframeRect = this.$refs.pdfViewer.getBoundingClientRect();// **修正点:确保 top 计算正确**const absoluteTop = rect.top + iframeRect.top;const absoluteLeft = rect.left + iframeRect.left + window.scrollX;// console.log(`iframeRect:`, iframeRect);// console.log(`selection rect:`, rect);// console.log(// `absoluteTop: ${absoluteTop}, absoluteLeft: ${absoluteLeft}`// );return {top: absoluteTop,left: absoluteLeft,width: rect.width,height: rect.height,};},updateToolPositionPdf(rect) {const viewportWidth = window.innerWidth;const tooltipWidth = '';this.selectionPosition = {top: `${rect.top - 70}px`,left: `${Math.min(Math.max(rect.left, 10),viewportWidth - tooltipWidth - 10)}px`,maxWidth: `${tooltipWidth}px`,};// console.log(`Final tooltip position:`, this.selectionPosition);},
- 部分样式-按钮样式
<style scoped lang="less">
::v-deep .selection-tools {position: fixed;background: rgba(29, 115, 232, 1);border-radius: 5px;box-shadow: 0 4px 12px rgba(25, 118, 210, 0.15);padding: 8px;display: inline-flex;align-items: center;// gap: 6px;z-index: 9999;// opacity: 0;transform: translateY(-10px) scale(0.95);transition: all 0.1s cubic-bezier(0.4, 0, 0.2, 1);/* 移除默认的pointer-events限制 */pointer-events: auto !important;/* 修正激活状态逻辑 */&::after {content: '';position: absolute;bottom: -11px;left: 50%;transform: translateX(-50%);border: 6px solid transparent;border-top-color: #1d73e8;filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));}&.active {opacity: 1;transform: translateY(0) scale(1);}.tool-item {padding: 6px 12px;border-radius: 4px;cursor: pointer;display: flex;align-items: center;transition: all 0.2s;flex-direction: column;height: 50px;justify-content: space-between;// &:hover {// background: #f0f6ff;// transform: translateY(-1px);// .iconfont {// color: #0065cc;// }// .tool-text {// color: #003d82;// }// }.tool-text {font-family: Microsoft YaHei;font-weight: 400;font-size: 12px;color: #ffffff;// line-height: 41px;}}.tool-divider {width: 1px;height: 38px;background: #3f86dd;margin: 0 4px;}img {max-width: 24px;}
}
</style>
以上方法可能导致的问题就是如果选择内容超出了pdf框,会导致按钮显示不出来,所以改为选择之后的鼠标最后出现的位置上面,修改代码handleTextSelectionPdf,updateToolPositionPdf
pdf位置计算核心方法-方案2
handleTextSelectionPdf(e) {clearTimeout(this.selectionTimer);// 捕获鼠标坐标(相对视口)const mouseX = e.clientX;const mouseY = e.clientY;this.selectionTimer = setTimeout(() => {// 获取 PDF iframe 的文档对象const pdfWindow = this.$refs.pdfViewer.contentWindow;const pdfDocument = pdfWindow.document;// 计算 PDF 容器在页面中的位置const iframeRect = this.$refs.pdfViewer.getBoundingClientRect();// console.log(mouseX, 'mouseX');// console.log(mouseY, 'mouseY');// console.log(iframeRect.left, 'iframeRect.left');// console.log(iframeRect.top, 'iframeRect.top');// console.log(pdfWindow.scrollX, 'pdfWindow.scrollX');// console.log(pdfWindow.scrollY, 'pdfWindow.scrollY');// console.log(window.scrollX, 'window.scrollX');// console.log(window.scrollY, 'window.scrollY');// 转换坐标到 PDF 文档坐标系const pdfX = mouseX;const pdfY = mouseY;// 在 PDF 文档中检测元素const targetElement = pdfDocument.elementFromPoint(pdfX, pdfY);const isValid = targetElement?.closest('#viewerContainer');// 其他验证逻辑保持不变const selection = pdfWindow.getSelection();const isTextValid =selection.rangeCount > 0 &&!selection.isCollapsed &&selection.toString().trim().length >= 1;if (isValid && isTextValid) {// 使用鼠标坐标定位工具框const viewportX = mouseX + iframeRect.left;const viewportY = mouseY + iframeRect.top;this.updateToolPositionPdf(viewportX, viewportY);this.selectionToolsVisible = true;this.tempSelection = selection.toString();} else {this.selectionToolsVisible = false;}}, 50);},updateToolPositionPdf(absoluteX, absoluteY) {const tooltipWidth = ''; // 根据实际工具框宽度调整// 边界检测逻辑let finalLeft = absoluteX - 35;let finalTop = absoluteY - 62.7 - 20; // 减去按钮元素高度 然后上移一点this.selectionPosition = {top: `${finalTop}px`,left: `${finalLeft}px`,maxWidth: `${tooltipWidth}px`,};console.log(this.selectionPosition);},