聊一聊文本溢出打点省略

前言

业务上经常会遇到 文本内容超出容器区域进行省略打点展示 的需求,而展示省略也存在多种场景和形式,今天我们来聊一聊这些细节。

  1. 单行文本超长打点省略(CSS)
  2. 多行文本超长打点省略(CSS)
  3. 单行/多行文本超长打点省略(JS)
  4. 单行/多行文本超长在 中间 位置打点省略(JS)
  5. 超长元素块整块打点省略(CSS)
  6. 自定义打点省略标签(JS)

一、单行文本超长打点省略(CSS)

CSS 属性支持对单行文本超长时在 尾部 进行打点省略。为文本容器设置以下属性:

.text-container{
  /* 明确指定容器的宽度 */
  width: 200px;
  /* 强制将文字排列在一行,不进行换行 */
  white-space: nowrap;
  /* 超出隐藏 */
  overflow: hidden;
  /* 在文本溢出时,显示省略符号来代表被修剪的文本 */
  text-overflow: ellipsis;
}

二、多行文本超长打点省略(CSS)

对于多行文本的超长省略,可使用 -webkit-line-clamp 来指定显示文本的行数,比如下面 两行文本超出进行打点省略。

.text-container{
  /* 明确指定容器的宽度 */
  width: 200px;
  /* 超出隐藏 */
  overflow: hidden;
  /* 在文本溢出时,显示省略符号来代表被修剪的文本 */
  text-overflow: ellipsis;
  /* 限制文本展示的行数 */
  -webkit-line-clamp: 2;
  /* 结合 -webkit-line-clamp 使用,将对象作为弹性伸缩盒子模型显示 */
  display: -webkit-box;
  /* 设置伸缩盒对象的子元素的排列方式 */
  -webkit-box-orient: vertical;
}

但它的短板在于 兼容性一般-webkit-line-clamp 属性只有 WebKit 内核的浏览器才支持,多适用于移动端页面(移动设备浏览器更多是基于 WebKit 内核)。

三、单行/多行文本超长打点省略(JS)

选用 JS 来实现更多的是要解决 CSS 多行文本超出省略 的兼容问题,这里的实现同样适用于 单行文本超出省略。

其原理是根据文本字符的长度,根据容器 font-size 大小来计算能够容纳多少文本字符,若无法全部容纳则对文本进行截断展示。

<style>
  .text-container{
    width: 200px;
  }
</style>

<div class="text-container"></div>

<script>
  // const text = 'this is a long text. this is a long text. this is a long text. this is a long text. this is a long text. this is a long text. this is a long text. ';
  const text = '这是一段超长的文本。这是一段超长的文本。这是一段超长的文本。这是一段超长的文本。这是一段超长的文本。这是一段超长的文本。';
  const lineNum = 2;
  const container = document.querySelector('.text-container');

  let { width, fontSize } = window.getComputedStyle(container);
  width = +width.slice(0, -2); // 拿到的值是 string 带 px,这里转成 number
  fontSize = +fontSize.slice(0, -2);

  // 1. 按中英文计算文本字符长度(中文两个字符,英文)
  const textLength = computedTextLength(text);
  // 2. 计算容器一行所能容纳的字符数
  const lineCharNum = Math.floor(width / fontSize) * 2;
  // 多行可容纳总字数
  const totalStrNum = Math.floor(lineCharNum * lineNum);

  // 内容截取
  let content = '';
  if (textLength > totalStrNum) {
    const lastIndex = totalStrNum - textLength;
    content = sliceTextLength(text, totalStrNum - 3).concat('...'); // ... 代表三个字符
  } else {
    content = text;
  }

  container.innerHTML = content;
</script>

computedTextLengthsliceTextLength 会将 中文 按照两个字符计算。

const computedTextLength = text => {
  let length = 0;
  for (let i = 0; i < text.length; i ++) {
    if (text.charCodeAt(i) < 0 || text.charCodeAt(i) > 255) {
      length += 2;
    } else {
      length += 1;
    }
  }
  return length;
}

const sliceTextLength = (text, sliceLength) => {
  let length = 0, newText = '';
  for (let i = 0; i < text.length; i ++) {
    if (text.charCodeAt(i) < 0 || text.charCodeAt(i) > 255) {
      length += 2;
    } else {
      length += 1;
    }
    if (length <= sliceLength) {
      newText += text[i];
    } else {
      break;
    }
  }
  return newText;
}

同样,JS 实现也存在短板:省略号展示的位置存在计算偏差(一个 font-size 的宽度对应两个字符存在一定偏差(字母 和 空格)),并不会像 CSS 能够将省略展示位置刚刚好。

四、单行/多行文本超长在 中间 位置打点省略(JS)

这类需求常见的场景是对 超长的文件名称进行中间位置省略,这样可以展示出文件后缀类型。如:这是一个很长很长...很长很长的文件.pdf

CSS 对超长文本省略的打点位置只能在末尾,采用 JS 实现可以控制省略打点位置,我们改造一下上面 「单行/多行文本超长打点省略(JS)」 截取逻辑。

if (textLength > totalStrNum) {
  // const lastIndex = totalStrNum - textLength;
  // content = sliceTextLength(text, totalStrNum - 3).concat('...'); // ... 代表三个字符

  const middleIndex = totalStrNum / 2;
  content = `${sliceTextLength(text, middleIndex)}...${lastSliceTextLength(text, middleIndex - 3)}`;
}

const lastSliceTextLength = (text, sliceLength) => {
  let length = 0, newText = '';
  for (let i = text.length - 1; i > 0; i --) {
    if (text.charCodeAt(i) < 0 || text.charCodeAt(i) > 255) {
      length += 2;
    } else {
      length += 1;
    }
    if (length <= sliceLength) {
      newText = text[i] + newText;
    } else {
      break;
    }
  }
  return newText;
}

五、超长元素块整块打点省略(CSS)

上面涉及的场景都是容器内渲染纯文本进行省略展示,假如容器内是行内块元素(业务上的数据标签),如何实现一行排不下时整个标签被移除进行打点省略展示呢(非 标签展示了一部分被截取)?

容器内 DOM 结构如下:

<div class="container">
  <span class="tag">前端</span>
  <span class="tag">后端</span>
  <span class="tag">测试</span>
  <span class="tag">UI</span>
  <span class="tag">产品</span>
</div>

要实现省略需要两步操作:

第一步,为容器设置内容超出打点省略:

.container{
  width: 200px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

第二步,将 span 元素设置为 display: inline-block,这样才能实现标签在排不下时,将标签整块都省略,而不是让标签只显示一部分打点省略。

.tag{
  display: inline-block;
}

效果图如下:

image.png

但是经过测试会发现 iOS 及 safari 浏览器下标签并未按照整块进行省略,解决办法是使用 多行省略替代单行省略

.container{
  width: 200px;
  /* white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis; */

  /* 注意这里使用 normal */
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
}

六、自定义打点省略标签(JS)

现在需求进行了升级,期望省略的 ... 能够有操作:移入或点击 能够查看所有 tag。类似下方示意图,需要我们将 打点省略 作为一个元素去实现。

image.png

这种情况我们需要采用 JS 来实现。首先渲染节点和样式,

<style>
  *{
    padding: 0;
    margin: 0;
  }
  .container{
    margin: 100px auto;
    width: 180px;
    /* 容器要指定高度 */
    height: 28px;
    /* 容器要设置相对定位 */
    position: relative;
  }
  .tag{
    display: inline-block;
    padding: 2px 4px;
    border-radius: 2px;
    background: purple;
    color: #fff;
    margin-right: 4px;
  }
  .tag:last-child{
    margin-right: 0;
  }
</style>
<div class="container"></div>

<script>
  const createTagEl = text => {
    const span = document.createElement('span');
    span.className = "tag";
    span.innerHTML = text;
    return span;
  }
  const tags = ['前端', '后端', '测试', 'UI', '产品'];
  const container = document.querySelector('.container');
  tags.forEach(tag => container.appendChild(createTagEl(tag)));
</script>

然后根据标签内容动态计算是否需要添加 打点省略 元素(可参考代码注释)。

...
const ellipsisWidth = 22 + 4; // 假设省略打点元素的宽度为 22,左边距为 4
const { offsetWidth, clientHeight, scrollHeight, children } = container; // 容器要指定宽高

// 需要打点省略
if (scrollHeight > clientHeight) {
  // 找到第一个被换行的 tag 索引(基于距离父元素的 偏移量 来计算)
  let sliceEndIndex = Array.from(children).findIndex(child => child.offsetTop !== 0);

  // 如果第一行排不下 ellipsis tag,调整 sliceEndIndex 索引
  while (sliceEndIndex > 0) {
    const child = children[sliceEndIndex - 1];
    // 剩余的空间可以用于展示 ellipsis tag
    if (offsetWidth - (child.offsetWidth + child.offsetLeft) >= ellipsisWidth) {
      break;
    }
    sliceEndIndex --; // 要删除标签
  }

  // 删除排不下的 tag
  let length = children.length - sliceEndIndex;
  while (length > 0) {
    container.removeChild(children[children.length - 1]);
    length --;
  }
  // 创建 ellipsis tag
  container.appendChild(createTagEl('...'));
}

参考

1. 小技巧!CSS 整块文本溢出省略特性探究
2. 可能是最全的 “文本溢出截断省略” 方案合集

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务