当前位置: 首页 > news >正文

前端-选中pdf中的文字并使用,显示一个悬浮的翻译按钮(本地pdfjs+iframe)不适用textlayer

使用pdfjs移步–

vue2使用pdfjs-dist实现pdf预览(iframe形式,不修改pdfjs原来的ui和控件,dom层可以用display去掉一部分组件)

方案1:获取选择文本内容的最前面的字符坐标的位置(这种写法会导致如果选择超出pdf容器的高度之后,导致按钮显示不出来,这种方法显示位置固定)

方案2:获取选择文本最后鼠标离开位置的坐标(目前没发现bug,这种方法显示的位置不固定)这个方法在最下面,核心位置计算方法handleTextSelectionPdf,updateToolPositionPdf

  1. 实现案例
    在这里插入图片描述
  2. pdf容器创建,悬浮盒子创建
	<iframe
	:src="pdfurl"
			class="pdfContent"
			ref="pdfViewer"
			frameborder="0"
			width="100%"
			height="850px"
		></iframe>
		<div
			v-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>
  1. data实例
	data() {
		return {

			selectionToolsVisible: false,
			selectionPosition: { top: '0px', left: '0px' },
			selectionTimer: null,
		};
	},
  1. mounted注册鼠标事件-注册pdf的监听
	this.$refs.pdfViewer.onload = () => {
			const iframeDoc =
				this.$refs.pdfViewer.contentDocument ||
				this.$refs.pdfViewer.contentWindow.document;

			iframeDoc.addEventListener('mouseup', this.handleTextSelectionPdf);
		};
  1. 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);
		},
  1. 部分样式-按钮样式
<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);
		},

相关文章:

  • 嵌入式面经(2)——央企篇
  • 医学图像白血病分割数据集labelme格式245张5类别
  • ES聚合学习(三)
  • git上传文件到远程库
  • 解决 uniapp 开发中权限申请同步告知目的问题| 华为应用商店上架审核问题解决
  • 安装和管理最新的Python3环境(以Mac为例)
  • Docker 数据卷管理
  • Spring 如何管理 Bean 的生命周期?
  • 11--华为防火墙Easy-IP实现原理与配置全解:从“翻译官“到“流量导演“的奇幻之旅(包你看一遍全记住)
  • Django REST Framework 请求封装源码解析与实现流程
  • 1、环境初始化--Linux安装dockerCE
  • Java 集合框架:从数据结构到性能优化,全面解析集合类
  • JavaScript基础-API 和 Web API
  • HIVE安装(详细教程)
  • Python实战(4)-网络编程
  • Liunx系统Microsoft SQL Server数据库还原
  • datawhale组队学习-大语言模型-task5:主流模型架构及新型架构
  • CentOS与Rocky 命令区别
  • python如何获取html中附件链接,并下载保存附件
  • 1.向量数据库milvus standalone单机版搭建
  • 澎湃研究所“营商环境研究伙伴计划”启动
  • 现场|万米云端,遇见上博
  • 抗战回望20︱《山西省战区抗敌行政工作检讨会议议决案》:“强民政治”、“说服行政”
  • 金融监管总局:支持银行有序设立科技金融专门机构,推动研发机器人、低空飞行器等新兴领域的保险产品
  • 象屿集团:对去化压力大、市场有效需求不足区域坚决暂停投资,打造多元上市路径
  • 景点变回监狱,特朗普下令重新启用“恶魔岛”