likes
comments
collection
share

前端工程师如何优雅地处理文本溢出?

作者站长头像
站长
· 阅读数 11

1、前言

前端工程师如何优雅地处理文本溢出?

“当文本的大小超出父级元素的大小区域时,文本就会有一部分显示在父级元素所在区域外部”,这就是文本溢出。文本溢出处理可以说是我们前端开发过程中最常遇到的场景之一,本文针对不同的文本溢出处理场景来探讨一下如何优雅地处理文本溢出,并给出相应的解决思路。

2、文本溢出纯 CSS 方案

与文本溢出相关的 CSS 属性主要包括:

  • overflow:定义当一个元素的内容太大而无法适应块级格式化上下文时候该做什么;
  • text-overflow:指导浏览器如何渲染溢出内容;
  • white-space:指导浏览器如何处理元素中的空白符;
  • word-break:指导浏览器怎样在单词内断行;
  • overflow-wrap:指导浏览器对于特别长的超出元素框宽度的单词是否可以从中间断开换行。

2.1 单行文本溢出处理

div {
  overflow: hidden; /* 文字长度超出限定宽度,则隐藏超出的内容 */
  text-overflow: ellipsis; /* 规定当文本溢出时显示省略符号来代表被修剪的文本 */
  white-space: nowrap; /* 多个连续空白符会被合并但换行无效,即设置文字在一行显示不能换行 */
}

前端工程师如何优雅地处理文本溢出?

2.2 多行文本溢出处理

2.2.1 使用 line-clamp 属性

div {
  overflow : hidden; /* 文字长度超出限定宽度,则隐藏超出的内容 */
  text-overflow: ellipsis; /* 规定当文本溢出时显示省略符号来代表被修剪的文本 */
  display: -webkit-box; /* 将对象作为弹性伸缩盒子模型显示 */
  -webkit-line-clamp: 2; /* 限制在一个块元素显示的文本的行数 */
  -webkit-box-orient: vertical; /* 设置伸缩盒模型的子元素的排列方式 */
}

缺点:只在 webkit 内核的浏览器才生效,存在兼容性问题。

2.2.2 使用伪元素

div {
  overflow: hidden;
  position: relative;
  line-height: 1em;
  height: 2em; /* height设置为行数×行高 */
  word-break: break-all;
}

div::after {
  content: "...";
  position: absolute;
  bottom: 0;
  right: 0;
  padding: 0px 10px;
	
	/* 背景色为纯白场景 */
  background-color: #ffffff;

	/* 背景色为白色透明渐变,展示效果更好 */
  background: -webkit-gradient(linear, left top, right top, from(rgba(255, 255, 255, 0)), to(white), color-stop(50%, white));
  background: -moz-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
  background: -o-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
  background: -ms-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
  background: linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
}

缺点:1. 省略号会一直显示;2. 省略号需要通过背景色来遮盖下方文本,不适合背景复杂场景;3. 不同字体下伪元素位置无法精确确定,可能出现文字只被隐藏半个的场景。

2.2.3 float 定位

前端工程师如何优雅地处理文本溢出?

设置三个浮动盒子,占位盒子为左浮动并且高度为父容器高度,文字盒子和省略盒子均为有浮动。如果文字盒子的文字较少,文字盒子高度未超出父容器,则省略盒子位于文字盒子下方;如果文字盒子的文字较多,文字盒子高度未超出父容器,发生了文本溢出,则省略盒子会被挤到占位盒子的下方

前端工程师如何优雅地处理文本溢出?

省略盒子在文字较少未发生文本溢出时,预期应该是隐藏的,因此我们需要对省略盒子增加样式left: 100%,而当文字较多发生文本溢出时,预期省略盒子应该位于父容器的右下角,因此我们需要对省略盒子增加样式transform: translate(-100%, -100%)移动其位置。

前端工程师如何优雅地处理文本溢出?

最后我们应该将超出父容器的内容隐藏,对父容器添加样式overflow: hidden,并且占位盒子我们预期应该不在父容器进行展示的,对文字盒子添加样式margin-left: -64px(值为占位盒子宽度)从而将占位盒子移出父容器之外。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        width: 400px;
        height: 90px;
        border: 3px solid;
        overflow: hidden;
      }
      .placeholder-box {
        background-color: brown;
        float: left;
        width: 64px;
        height: 100%;
      }
      .text-box {
        background-color: cadetblue;
        float: right;
        width: 100%;
        /* 等于占位盒子宽度 */
        margin-left: -64px;
      }
      .more-box {
        background-color: cornflowerblue;
        float: right;
        width: 64px;
        height: 20px;
        line-height: 20px;
        position: relative;
        left: 100%;
        transform: translate(-100%, -100%);
      }
    </style>
  </head>
  <body>
    <!-- 容器 -->
    <div class="box">
      <!-- 占位盒子 -->
      <div class="placeholder-box">占位盒子</div>
      <!-- 文字盒子 -->
      <div class="text-box">
        文字盒子 -
        这是一段很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的文字
      </div>
      <!-- 省略盒子 -->
      <div class="more-box">省略盒子</div>
    </div>
  </body>
</html>

缺点:省略号需要通过背景色来遮盖下方文本,不适合背景复杂场景。

3、文本溢出 JS 方案

纯 CSS 处理方案由浏览器原生来支持,通常适用于简单纯文本展示场景。但是,在实际中我们可能会面临各式各样的需求,此时就需要结合 JS 来判断文本是否发生溢出并进行文本溢出处理。

序号内容示例
1结合 tooltip 使用时需要知道是否发生文本溢出前端工程师如何优雅地处理文本溢出?
2自定义文本溢出的省略内容,而不是默认的“…”前端工程师如何优雅地处理文本溢出?
3容器内展示内容前后均有其他内容而并非纯文本前端工程师如何优雅地处理文本溢出?

3.1 判断文本是否溢出

3.1.1 通过宽高判断溢出(推荐)

目前最常用的文本溢出判断方案就是通过 scrollWidth/Height 和 clientWidth/Height 来进行判断,首先我们先来回顾一些基础知识:

clientXXXscrollXXX
元素内容 + 内边距元素内容 + 内边距 + 隐藏区域大小(如果有滚动的话)
前端工程师如何优雅地处理文本溢出?前端工程师如何优雅地处理文本溢出?

那么文本溢出的判断可以通过 scrollWidth/Height 和clientWidth/Height 的值比较来实现:

  • 单行文本溢出判断:文本所在的块元素的实际宽度 > 可视区的宽度,即clientWidth < dom.scrollWidth(注意,需要设置文本不换行,保证是单行!)
  • 多行文本溢出判断:文本所在的块元素的实际高度 > 可视区的高度,即dom.clientHeight < dom.scrollHeight

3.1.2 其他方式

3.1.2.1 通过 measureText 判断

CanvasRenderingContext2D.measureText() 方法返回一个关于被测量文本 TextMetrics 对象包含的信息,其中包括它的宽度。基于 canvas 的能力就能计算文本占据的宽度从而判断是否发生文本溢出,同时也能计算出每一行可以显示多少文本。该方法在实际中不太常用,具体内容和限制问题请移步 👉 参考资料,此处不再展开。

3.1.2.2 通过克隆 DOM 比较

克隆一份 DOM 副本,且副本不在浏览器中显示,仅用于比较两者的尺寸(即宽度或高度)。如果是一行展示场景则将副本的宽度限制取消,如果是多行展示则将副本的高度限制取消,然后比较尺寸,如果副本的尺寸大于元素本身尺寸,则表示溢出,否则未溢出。

3.2 确定文本溢出位置

确定文本溢出的位置最简单的方法就是一个一个字符去判断是否在当前字符位置发生溢出。归根结底,确定文本溢出位置一个搜索的过程,因此完全可以借助各种搜索算法来进行优化。

/* 截断方法:逐字符比较 */
const oneByOneTruncate = (dom: HTMLElement, text: string, maxHeight: number) => {
  let targetIndex = 0;
  for (let i = 0, len = text.length; i < len; i++) {
    dom.innerText = text.substring(0, i);
    if (dom.scrollHeight > maxHeight) {
      targetIndex = i;
      break;
    }
  }
  return targetIndex;
};
/* 截断方法:折半和逐字符比较相结合 */
const halfTruncate = (dom: HTMLElement, text: string, maxHeight: number) => {
  let targetIndex = text.length;
  while (dom.scrollHeight > maxHeight) {
    if (dom.scrollHeight > maxHeight * 3) {
      targetIndex = Math.floor(targetIndex / 2);
    } else {
      targetIndex = targetIndex - 1;
    }
    dom.innerText = text.substring(0, targetIndex);
  }
  return targetIndex;
};

3.3 JS 处理文本溢出

JS 处理文本溢出指的是通过截断溢出文本并直接添加省略号作为展示文本,使得浏览器真正渲染出来的文本不会发生溢出,该方法的关键在于判断在哪个字符发生了溢出,也就是找到溢出的位置来将文本进行截断。主要步骤可以分为 3 步:

前端工程师如何优雅地处理文本溢出?

  1. 确定展示内容的容器的最大宽度或高度(可根据行数确定或直接设置等)
  2. 判断是否发生文本溢出(文本溢出判断详见《3.1 判断文本是否溢出》)
  3. 查找溢出位置并截断文本(溢出位置查找详见《3.2 确定文本溢出位置》)

4、文本溢出组件

4.1 基于原生 CSS 方案组件

组件中单/多行文本溢出均采用原生 CSS 方案进行处理。

<template>
  <div
    :class="['native-text-overflow', isMultiLine.value ? 'multi-line' : 'one-line']"
    :style="{ '-webkit-line-clamp': isMultiLine.value ? props.lineNum : '' }"
  >
    <slot>{{ content }}</slot>
  </div>
</template>

<script lang="ts" setup>
import { ref, computed, StyleValue } from "vue";

const props = withDefaults(
  defineProps<{
    content?: string;
    lineNum?: number;
  }>(),
  {
    content: "",
    lineNum: 1,
  }
);

const isMultiLine = computed(() => {
  return props.lineNum > 1;
});
</script>

<style lang="less" scoped>
.native-text-overflow {
  &.one-line {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  &.multi-line {
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    word-break: break-all;
  }
}
</style>
💡 如果需要对外暴露当前是否文本发生溢出,可结合宽高判断溢出并监听 DOM 变化。

4.2 基于 JS 文本截断组件

基于 JS 文本截断组件(Vue3 版)已发布至 npm 👉 vue3-text-clamp 欢迎使用 🎉 

  • 支持选择限制行数与/或最大高度,在文本溢出时截断文本。
  • 支持自定义文本截断时显示的省略字符以及截断的位置。
  • 支持在布局变化时自动更新。
  • 支持展开/收起被截断部分内容。
  • 支持自定义截断文本前后内容,并且进行响应式更新。

5、总结

文本溢出的处理可以说是我们每一个前端都必须掌握的能力,本文主要介绍了文本溢出处理中的纯 CSS 方案和 JS 方案,并在最后给出了相应的 Vue3 组件实现,根据不同的使用场景可以选择合适的方案来进行解决。

参考资料