主要实现
文本超长时悬浮展示完整内容,不超长时不显示悬浮窗” 的交互,且悬浮窗样式需完全自定义(避免浏览器原生 title 样式不可控的问题)。本文基于 Vue3+CSS实现这一需求,涵盖单行 / 多行文本溢出省略号、智能判断文本是否超长、自定义悬浮窗样式、悬浮窗交互优化等核心要点。
实现效果
需求分析
- 文本超出指定行数(如 5 行)时,显示省略号;未超出时正常展示。
- 文本超长时,鼠标 hover触发悬浮窗展示完整内容;
- 未超长时,无悬浮窗、无鼠标指针提示。
- 悬浮窗样式完全自定义(背景、字体、阴影、宽高),且不占用原容器高度。
- 鼠标移入悬浮窗时,悬浮窗不消失;鼠标离开文本 / 悬浮窗后,悬浮窗延迟隐藏(避免快速划过导致体验差)。
- 窗口大小变化时,重新计算文本高度,适配响应式布局
实现方法
1.单行
.single-line-ellipsis{width:100%;white-space:nowrap;/* 强制单行 */overflow:hidden;/* 隐藏溢出内容 */text-overflow:ellipsis;/* 溢出显示省略号 */}2. CSS 多行文本溢出省略号(webkit 内核)
适用于 Chrome/Edge/Safari 等 webkit 内核浏览器,主要通过-webkit-line-clamp设置文本行数
.multi-line-ellipsis{width:100%;line-height:1.5em;font-size:14px;display:-webkit-box;-webkit-line-clamp:5;/* 限制显示行数 */-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;}3.悬浮窗超长展示/不超长隐藏
本文以固定5行文本为例
关键
- 克隆容器:通过隐藏的克隆容器渲染完整文本,计算其实际高度,判断是否超出指定行数的高度。
- 使用Teleport组件:将悬浮窗渲染到下,脱离原容器布局流,避免悬浮窗占用原容器高度。
- 鼠标交互优化:延迟判断mouseleave事件,避免鼠标快速划过文本时悬浮窗闪显闪藏;标记鼠标是否移入悬浮窗,确保悬浮窗内操作时不消失。
实现
1.基础布局与 CSS 样式
先实现文本溢出省略号的基础样式,同时准备 “克隆容器”(用于计算完整文本高度,隐藏不显示)
<template><!--外层容器:承载显示文本+克隆容器--><divclass="business-scope-container"ref="scopeContainer"@mouseenter="() => isTextOverFlow && (showScopePopover = true, updatePopoverFixedPos())"@mouseleave="handleContainerLeave"><!--显示用:多行文本溢出省略号--><divclass="text-limit-5lines"v-html="detailData.businessScope"ref="textContentRef"/><!--隐藏用:克隆容器,计算完整文本高度--><divclass="text-clone"ref="textCloneRef"v-html="detailData.businessScope"/></div><!--自定义悬浮窗:teleport到body,脱离原布局流--><teleport to="body"><divclass="custom-scope-popover"v-show="isTextOverFlow && showScopePopover":style="popoverFixedStyle"@mouseenter="isHoverPopover = true"@mouseleave="handlePopoverLeave"><divclass="popover-content"v-html="detailData.businessScope"/></div></teleport></template><style scoped>/* 外层容器:避免溢出截断悬浮窗 */.business-scope-container{width:100%;height:fit-content;/* 高度仅适配显示文本,不包含悬浮窗 */overflow:visible!important;position:relative;}/* 多行文本溢出省略号(核心样式) */.text-limit-5lines{width:100%;line-height:1.5em;font-size:14px;display:-webkit-box;-webkit-line-clamp:5;/* 限制5行,可自定义 */-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;max-height:calc(1.5em*5);/* 5行最大高度,与-webkit-line-clamp对应 */cursor:default;/* 默认无指针,超长时通过JS改为pointer */}/* 克隆容器:隐藏,仅用于计算完整文本高度 */.text-clone{position:absolute;top:0;left:0;width:100%;line-height:1.5em;/* 与显示容器一致 */font-size:14px;/* 与显示容器一致 */visibility:hidden;/* 隐藏但保留布局,用于计算高度 */white-space:pre-wrap;/* 保留换行符,计算真实高度 */word-break:break-all;/* 超长文本换行,保证高度计算准确 */pointer-events:none;/* 不响应鼠标事件,避免干扰 */height:auto;overflow:visible;}/* 自定义悬浮窗样式(完全可控) */.custom-scope-popover{position:fixed;/* 固定定位,脱离原布局流 */z-index:3000;/* 避免被其他元素覆盖 */max-width:500px;max-height:300px;padding:12px;background:#fff;border:1px solid #e5e7eb;border-radius:6px;box-shadow:04px16pxrgba(0,0,0,0.15);box-sizing:border-box;overflow-y:auto;/* 超长内容内部滚动 */pointer-events:auto;/* 允许鼠标悬停/选中文本 */}/* 悬浮窗内容样式 */.popover-content{line-height:1.6;white-space:pre-wrap;/* 保留换行符 */word-break:break-all;color:#333;font-size:14px;}</style>2.判断文本是否超长 + 悬浮窗控制
核心:通过克隆容器的scrollHeight(完整文本高度)与显示容器的maxHeight(指定行数的最大高度)对比,判断文本是否超长;结合鼠标事件控制悬浮窗显隐。
<script setup>import{ref,watch,nextTick}from'vue';// 模拟业务数据(如接口返回的经营范围)constdetailData=ref({businessScope:`第一行:经营范围示例 第二行:计算机软硬件销售、技术服务 第三行:电子产品研发、生产、销售 第四行:货物进出口、技术进出口 第五行:企业管理咨询、商务信息咨询 第六行:超出5行的内容,悬浮窗展示完整文本 第七行:支持HTML格式文本,比如<br/>换行标签 第八行:鼠标移入悬浮窗可正常选中文本`});// 标记文本是否超过5行constisTextOverFlow=ref(false);// 悬浮窗显隐控制constshowScopePopover=ref(false);// 标记鼠标是否悬停在悬浮窗上(避免移入悬浮窗后消失)constisHoverPopover=ref(false);// 容器constscopeContainer=ref(null);// 显示文本consttextContentRef=ref(null);// 克隆文本consttextCloneRef=ref(null);// 悬浮窗固定位置样式constpopoverFixedStyle=ref({left:'0px',top:'0px'});/** * 计算文本是否超过指定行数的核心函数 * 原理:克隆容器渲染完整文本,对比其高度与显示容器的最大高度 */constcalcTextOverFlow=()=>{if(!textContentRef.value||!textCloneRef.value)return;// 获取显示容器的最大高度(与CSS中的max-height一致)constmaxHeight=parseFloat(getComputedStyle(textContentRef.value).maxHeight);// 获取克隆容器的完整文本高度constactualHeight=textCloneRef.value.scrollHeight;// 更新是否超行的标记isTextOverFlow.value=actualHeight>maxHeight;// 未超行时,强制隐藏悬浮窗if(!isTextOverFlow.value){showScopePopover.value=false;isHoverPopover.value=false;}// 未超行时移除鼠标指针提示textContentRef.value.style.cursor=isTextOverFlow.value?'pointer':'default';};/** * 计算悬浮窗固定位置(显示在文本容器下方10px) */constupdatePopoverFixedPos=()=>{if(!scopeContainer.value||!isTextOverFlow.value)return;constrect=scopeContainer.value.getBoundingClientRect();// 获取容器视口位置popoverFixedStyle.value={left:`${rect.left}px`,top:`${rect.bottom+10}px`,// 文本下方10px显示maxWidth:`${Math.min(500,document.documentElement.clientWidth-rect.left-20)}px`// 避免超出视口右侧};};/** * 延迟判断是否隐藏悬浮窗 * 延迟100ms,鼠标可移入 */consthandleContainerLeave=()=>{if(!isTextOverFlow.value)return;setTimeout(()=>{if(!isHoverPopover.value){showScopePopover.value=false;}},100);};/** * 隐藏悬浮窗并重置标记 */consthandlePopoverLeave=()=>{isHoverPopover.value=false;showScopePopover.value=false;};/** * 监听文本数据变化,重新计算是否超行 */watch(()=>detailData.businessScope,async()=>{awaitnextTick();calcTextOverFlow();},{immediate:true});/** * 重新获取文本高度和悬浮窗位置 */window.addEventListener('resize',async()=>{awaitnextTick();calcTextOverFlow();if(isTextOverFlow.value&&showScopePopover.value){updatePopoverFixedPos();}});</script>4.注意
克隆容器的字号、行高、宽度必须与显示容器完全一致,否则高度计算错误。