CSS实现单行、多行文本超长显示 / 不超长隐藏、悬浮窗超长展示/不超长隐藏、悬浮窗手动控制样式

主要实现

文本超长时悬浮展示完整内容,不超长时不显示悬浮窗" 的交互,且悬浮窗样式需完全自定义(避免浏览器原生 title 样式不可控的问题)。本文基于 Vue3+CSS 实现这一需求,涵盖单行 / 多行文本溢出省略号、智能判断文本是否超长、自定义悬浮窗样式、悬浮窗交互优化等核心要点。

实现效果


需求分析

  1. 文本超出指定行数(如 5 行)时,显示省略号;未超出时正常展示。
  2. 文本超长时,鼠标 hover触发悬浮窗展示完整内容;
  3. 未超长时,无悬浮窗、无鼠标指针提示。
  4. 悬浮窗样式完全自定义(背景、字体、阴影、宽高),且不占用原容器高度。
  5. 鼠标移入悬浮窗时,悬浮窗不消失;鼠标离开文本 / 悬浮窗后,悬浮窗延迟隐藏(避免快速划过导致体验差)。
  6. 窗口大小变化时,重新计算文本高度,适配响应式布局

实现方法

1.单行

typescript 复制代码
.single-line-ellipsis {
  width: 100%;
  white-space: nowrap; /* 强制单行 */
  overflow: hidden; /* 隐藏溢出内容 */
  text-overflow: ellipsis; /* 溢出显示省略号 */
}

2. CSS 多行文本溢出省略号(webkit 内核)

适用于 Chrome/Edge/Safari 等 webkit 内核浏览器,主要通过-webkit-line-clamp设置文本行数

typescript 复制代码
.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 样式

先实现文本溢出省略号的基础样式,同时准备 "克隆容器"(用于计算完整文本高度,隐藏不显示)

typescript 复制代码
<template>
  <!-- 外层容器:承载显示文本+克隆容器 -->
  <div class="business-scope-container" ref="scopeContainer" 
       @mouseenter="() => isTextOverFlow && (showScopePopover = true, updatePopoverFixedPos())"
       @mouseleave="handleContainerLeave">
    <!-- 显示用:多行文本溢出省略号 -->
    <div class="text-limit-5lines" v-html="detailData.businessScope" ref="textContentRef" />
    <!-- 隐藏用:克隆容器,计算完整文本高度 -->
    <div class="text-clone" ref="textCloneRef" v-html="detailData.businessScope" />
  </div>

  <!-- 自定义悬浮窗:teleport到body,脱离原布局流 -->
  <teleport to="body">
    <div class="custom-scope-popover" 
         v-show="isTextOverFlow && showScopePopover" 
         :style="popoverFixedStyle"
         @mouseenter="isHoverPopover = true"
         @mouseleave="handlePopoverLeave">
      <div class="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: 0 4px 16px rgba(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(指定行数的最大高度)对比,判断文本是否超长;结合鼠标事件控制悬浮窗显隐。

typescript 复制代码
<script setup>
import { ref, watch, nextTick } from 'vue';

// 模拟业务数据(如接口返回的经营范围)
const detailData = ref({
  businessScope: `第一行:经营范围示例
第二行:计算机软硬件销售、技术服务
第三行:电子产品研发、生产、销售
第四行:货物进出口、技术进出口
第五行:企业管理咨询、商务信息咨询
第六行:超出5行的内容,悬浮窗展示完整文本
第七行:支持HTML格式文本,比如<br/>换行标签
第八行:鼠标移入悬浮窗可正常选中文本`
});

// 标记文本是否超过5行
const isTextOverFlow = ref(false);
// 悬浮窗显隐控制
const showScopePopover = ref(false);
// 标记鼠标是否悬停在悬浮窗上(避免移入悬浮窗后消失)
const isHoverPopover = ref(false);
// 容器
const scopeContainer = ref(null);
// 显示文本
const textContentRef = ref(null);
// 克隆文本
const textCloneRef = ref(null);
// 悬浮窗固定位置样式
const popoverFixedStyle = ref({ left: '0px', top: '0px' });

/**
 * 计算文本是否超过指定行数的核心函数
 * 原理:克隆容器渲染完整文本,对比其高度与显示容器的最大高度
 */
const calcTextOverFlow = () => {
  if (!textContentRef.value || !textCloneRef.value) return;

  // 获取显示容器的最大高度(与CSS中的max-height一致)
  const maxHeight = parseFloat(getComputedStyle(textContentRef.value).maxHeight);
  // 获取克隆容器的完整文本高度
  const actualHeight = 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)
 */
const updatePopoverFixedPos = () => {
  if (!scopeContainer.value || !isTextOverFlow.value) return;
  const rect = 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,鼠标可移入
 */
const handleContainerLeave = () => {
  if (!isTextOverFlow.value) return;
  setTimeout(() => {
    if (!isHoverPopover.value) {
      showScopePopover.value = false;
    }
  }, 100);
};

/**
 * 隐藏悬浮窗并重置标记
 */
const handlePopoverLeave = () => {
  isHoverPopover.value = false;
  showScopePopover.value = false;
};

/**
 * 监听文本数据变化,重新计算是否超行
 */
watch(
  () => detailData.businessScope,
  async () => {
    await nextTick();
    calcTextOverFlow();
  },
  { immediate: true }
);

/**
 * 重新获取文本高度和悬浮窗位置
 */
window.addEventListener('resize', async () => {
  await nextTick();
  calcTextOverFlow();
  if (isTextOverFlow.value && showScopePopover.value) {
    updatePopoverFixedPos();
  }
});
</script>

4.注意

克隆容器的字号、行高、宽度必须与显示容器完全一致,否则高度计算错误。

相关推荐
blackorbird2 小时前
苹果修复了两个在定向攻击中被利用的Webkit漏洞,其中一个与谷歌ANGLE漏洞同源
前端·webkit
席之郎小果冻2 小时前
【04】【创建型】【聊一聊,建造者模式】
java·前端·建造者模式
风无雨2 小时前
在 React 中实现数学公式显示:使用 KaTeX 和 react-katex
前端·react.js·前端框架
zfj3212 小时前
vscode是js开发的,为什么能支持golang java等各种语言开发
javascript·vscode·golang
GDAL2 小时前
Mapbox GL JS 核心表达式:`==` 相等判断完全教程
javascript·mapbox
二两锅巴2 小时前
📺 无需Electron!前端实现多显示器浏览器窗口精准控制与通信
前端
炸土豆2 小时前
防抖节流里的this传递
前端·javascript
用户4099322502123 小时前
Vue3中动态样式数组的后项覆盖规则如何与计算属性结合实现复杂状态样式管理?
前端·ai编程·trae
山璞3 小时前
Flutter3.32 中使用 webview4.13 与 vue3 项目的 h5 页面通信,以及如何调试
前端·flutter