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

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

使用pdfjs移步–

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

  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监听代码
		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>


http://www.kler.cn/a/594807.html

相关文章:

  • 蓝桥杯练习day3:反转字符串II
  • 07. 面向对象高级(2)_设计模式
  • 【QA】观察者模式在QT有哪些应用?
  • 动力保护板测试仪:电池安全的坚实守护者
  • 01-Spring中的循环依赖以及它是如何解决的
  • 健康医疗:动态代理 IP 保障医疗数据安全,提升远程医疗服务质量!
  • LeetCode算法题(Go语言实现)_03
  • 详解string类+迭代器
  • 【记录】使用 Docker 搭建 MongoDB 分布
  • 嵌入式软件开发--面试总结
  • 工具层handle_response
  • Facebook和心理健康:社交媒体对我们情绪的影响
  • 如何在ubuntu上安装pig数据库
  • 快速了解以太坊多种代币标准
  • 前端进化:从焦虑到机遇的范式转型之路
  • navicat 创建Oracle连接报错:ora28040 没有匹配的验证协议
  • TCP/UDP传输过程
  • AI辅助的逆向分析
  • MutableList 和 ArrayList 区别
  • 画出ConcurrentHashMap 1.8的put流程图,记住CAS和synchronized的结合