使用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);},