文字中的省略号(Mac 风格)

71

我需要在可调整大小的元素中间实现省略号("...")。这里是可能的样子。所以,

"Lorem ipsum dolor sit amet. Ut ornare dignissim ligula sed commodo."

变成

"Lorem ipsum dolor sit amet ... commodo."
当元素被拉伸至文本宽度时,我希望省略号消失。如何实现?

7
如果你有最终实现的话,我很乐意看一下。 :) - Paolo Bergantino
17个回答

48

在 HTML 中,将完整值放入自定义 data-* 属性中,例如:

<span data-original="your string here"></span>

然后将loadresize事件监听器分配给一个JavaScript函数,该函数将读取原始数据属性并将其放置在您的span标记的innerHTML中。以下是省略函数的示例:

function start_and_end(str) {
  if (str.length > 35) {
    return str.substr(0, 20) + '...' + str.substr(str.length-10, str.length);
  }
  return str;
}

如果需要,调整值或尝试使其动态化以适应不同对象。如果你的用户使用不同的浏览器,你可以从dom中的其他位置使用相同字体和大小的文本来获取一个参考宽度。然后插值到适当数量的字符以供使用。

提示:在...或者信息中使用abbr标签,这样用户可以获得带有完整字符串的工具提示。

<abbr title="simple tool tip">something</abbr>

1
这最终成为了我最佳解决方案的基础。加上几行额外的JavaScript代码,它现在完全按照我的意愿运行。我已经决定使用“title”而不是自定义标签,这样就可以自然地提供可访问性。感谢Stefan、John和Kgiannakakis! - user102465
3
@user102465,你能分享最终结果吗?谢谢! - seangates
2
有没有仅使用CSS的方法?在UI本身中修复它,而不是使用JavaScript? - Kailas
2
你可以用str.substr(-10)来表示第二部分。 - rassoh
1
如果我们实现省略号并尝试复制应用省略号的内容,则复制的是省略号(...)而不是实际值。例如: 实际值:abcdefghij, 在UI上显示为:abcd...hij, 复制的值:abcd...hij如何在复制时获取实际值? - Nirav Panchal
substr已被弃用,您应该使用它。 - undefined

40
我想提出解决这个问题的示例。
主要思路是将文本分成两个相等的部分(如果长度为奇数,则第一个部分更长),其中一个部分在末尾有省略号,另一个部分使用text-overflow: clip右对齐。
因此,如果您想使它自动化/通用化,只需要使用js拆分字符串并设置属性即可。
然而,它也有一些缺点。
  1. 没有很好的按单词或甚至字母换行(text-overflow:''目前仅适用于FF)
  2. 如果拆分发生在单词之间,则空格应该在第一个部分。否则,它将被折叠。
  3. 字符串结尾不应该有任何感叹号,由于direction: rtl - 它们将移动到字符串的左侧。我认为,可以通过将单词的右侧放入标签中,并将感叹号放在::after伪元素中来修复这个问题。但我还没有正确地解决它。

但是,尽管有这些,它对我来说看起来非常酷,特别是当你拖动浏览器的边框时,你可以在jsfiddle页面上轻松完成:https://jsfiddle.net/extempl/93ymy3oL/。或者只需运行下面固定max-width的片段。

下面是Gif:

Gif

body {
  max-width: 400px;
}

span::before, span::after {
  display: inline-block;
  max-width: 50%;
  overflow: hidden;
  white-space: pre;
}

span::before {
  content: attr(data-content-start);
  text-overflow: ellipsis;
}

span::after {
  content: attr(data-content-end);
  text-overflow: '';
  direction: rtl;
}
<span data-content-start="Look deep into nature, and then you " 
      data-content-end=  "will understand everything better"></span>

<br>
<span data-content-start="https://www.google.com.ua/images/branding/g" 
      data-content-end=  "ooglelogo/2x/googlelogo_color_272x92dp.png"></span>


有没有针对Chrome的text-overflow: '';的解决方法?字符串不支持作为值; - jaysonder
1
很遗憾,目前还没有纯CSS的解决方法。它仍然只在Firefox中有效:https://developer.mozilla.org/en-US/docs/Web/CSS/text-overflow#Browser_compatibility - extempl
3
上面的代码片段在Chrome中似乎可以正常运行,尽管对于后半部分来说,文本剪切可能看起来有点奇怪。 - Llama D'Attore
我喜欢这个解决方案,因为它非常高效;没有 JavaScript。 - solidau

12

所以我的同事提出了一个解决方案,它不使用额外的DOM元素。我们检查div是否溢出,并添加最后n个字符的数据属性。剩余的工作在css中完成。

这里是一些HTML:


这里是一些HTML:
<div class="box">
    <div class="ellipsis" data-tail="some">This is my text it is awesome</div>
</div>
<div class="box">
    <div class="ellipsis">This is my text</div>
</div>

还有CSS:

.box {
    width: 200px;
}

.ellipsis:before {
    float: right;
    content: attr(data-tail);
}

.ellipsis {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

这里是必要的 jsfiddle 示例: http://jsfiddle.net/r96vB/1/


2
非常好的解决方案,但不幸的是在Firefox 34中它无法工作(文本重叠),而在IE < 10中又不支持querySelector方法。 - Rob Juurlink
1
我喜欢这个解决方案。适用于Safari(mac),FF 57,Edge和Chrome 66。 - Sam Axe
几乎可以了,但是JS需要在盒子宽度改变时每次执行,否则:after内容会保持不变 :( - frnhr

8
以下Javascript函数将进行中间截断,类似于OS X:
function smartTrim(string, maxLength) {
    if (!string) return string;
    if (maxLength < 1) return string;
    if (string.length <= maxLength) return string;
    if (maxLength == 1) return string.substring(0,1) + '...';

    var midpoint = Math.ceil(string.length / 2);
    var toremove = string.length - maxLength;
    var lstrip = Math.ceil(toremove/2);
    var rstrip = toremove - lstrip;
    return string.substring(0, midpoint-lstrip) + '...' 
    + string.substring(midpoint+rstrip);
}       

它将用省略号替换中间的字符。我的单元测试显示:

var s = '1234567890';
assertEquals(smartTrim(s, -1), '1234567890');
assertEquals(smartTrim(s, 0), '1234567890');
assertEquals(smartTrim(s, 1), '1...');
assertEquals(smartTrim(s, 2), '1...0');
assertEquals(smartTrim(s, 3), '1...90');
assertEquals(smartTrim(s, 4), '12...90');
assertEquals(smartTrim(s, 5), '12...890');
assertEquals(smartTrim(s, 6), '123...890');
assertEquals(smartTrim(s, 7), '123...7890');
assertEquals(smartTrim(s, 8), '1234...7890');
assertEquals(smartTrim(s, 9), '1234...67890');
assertEquals(smartTrim(s, 10), '1234567890');
assertEquals(smartTrim(s, 11), '1234567890');

2
这很棒,而且按照描述的方式工作,但是我需要使输出字符串“可伸缩”,以便截断是基于视口大小确定的。大屏幕-没有截断,窄屏幕-使用动态计算的最佳字符数量截断字符串。 - user102465
你需要将事件处理程序附加到其容器上,然后根据容器宽度属性计算适当的maxLength。如果你的字体族是等宽字体,那么这很容易;如果是比例字体,则需要进行实验以获得正确的启发式方法。 - johnvey
需要指出的是,这仅适用于固定宽度字体显示。在变宽字体中,文本的显示长度与字符长度并不完全相关。 - Jan Segre

5

经过一些flex box的研究,我发现了这个纯CSS解决方案,我认为它非常酷。

<div style="width:100%;border:1px solid green;display:inline-flex;flex-wrap:nowrap;">
   <div style="flex: 0 1 content;text-overflow: ellipsis;overflow:hidden;white-space:nowrap;"> Her comes very very very very very very very very very very very very very very very very very very very long </div>
   <div style="flex: 1 0 content;white-space:nowrap;"> &nbsp;but flexible line</div>
</div>

2
不要认为这回答了问题。这并没有解决在 1 字符串中间添加省略号的问题。这只是在两个字符串之间添加省略号。也就是说,在 @user102465 的情况下,必须先对字符串进行某种方式的拆分,才能使用您的解决方案。 - Laurens Rietveld

4
这可能有点晚了,但我正在寻找解决方案,一位同事建议了一个非常优雅的解决方案,我想分享一下。它需要一些JS,但并不多。
想象一下你有一个需要放置标签的大小为 div
<div style="width: 200px; overflow: hidden"></div>

现在,您有一个函数,它将采用两个参数:具有标签的字符串和DOM元素(此 div ),以适应其中:
function setEllipsisLabel(div, label) 

首先,您需要创建一个带有此标签的,并将其放入
中:

var span = document.createElement('span');
span.appendChild(document.createTextNode(label));
span.style.textOverflow = 'ellipsis';
span.style.display = 'inline-block';
div.appendChild(span);

我们将text-overflow属性设置为“ellipsis”,这样当文本被截断时,就会在末尾添加漂亮的“...”来说明。我们还将display设置为“inline-block”,以便稍后可以操纵这些元素的实际像素尺寸。到目前为止,这些都是利用纯CSS就能够完成的。

但是我们想要在中间使用省略号。首先,我们应该确定是否需要使用省略号……这可以通过比较div.clientWidthspan.clientWidth来完成——仅当spandiv宽时才需要省略号。

如果我们确实需要省略号,让我们首先假设我们希望显示单词末尾固定数量的字符——比如说10个字符。因此,让我们创建一个,其中只包含标签的最后10个字符,并将其插入到div中:

var endSpan = document.createElement('span');
endSpan.style.display = 'inline-block';
endspan.appendChild(document.createTextNode(label.substring(label.length - 10)));
div.appendChild(endSpan);

现在,让我们覆盖原始的宽度,以适应新的:
span.style.width = (div.clientWidth - endSpan.clientWidth) + 'px';

由此产生的结果是,我们现在有一个类似于以下结构的DOM结构:
<div style="width: 200px; overflow: hidden">
   <span style="display: inline-block; text-overflow: ellipsis; width: 100px">
      A really long label is shown in this span
   </span>
   <span style="display: inline-block"> this span</span>
</div>

因为第一个设置了"text-overflow"为"ellipsis",所以它会在末尾显示"...",然后是第二个的10个字符,导致省略号大约显示在
的中间。
您不需要为endSpan硬编码10个字符长度:可以通过计算的初始宽度与
的比例,从标签长度中减去适当比例并除以2来近似计算。

4

我看到这里的解决方案都没有考虑到不同字符具有不同的宽度。 我的解决方案考虑了这一点。基本上,它会逐个字符地剥离文本,直到符合要求。请看:

const span = document.getElementById("span");
const div = document.getElementById("div");
const originalText = span.textContent;
const textLength = originalText.length;
let part1 = originalText.substr(0, Math.floor(textLength / 2));
let part2 = originalText.substr(Math.floor(textLength / 2));
let trimPart1 = true;
while (span.clientWidth > div.clientWidth) {
  if (trimPart1) {
    part1 = part1.substr(0, part1.length - 1);
  } else {
    part2 = part2.substr(-1 * (part2.length - 1));
  }
  span.textContent = part1 + "..." + part2;
  trimPart1 = !trimPart1;
}
<div id="div" style="overflow: hidden; width: 200px; white-space: nowrap;">
  <span id="span" style="display: inline-block">this is a quite long text that has some words and I want it to be split in half</span>
</div>

https://jsfiddle.net/maxim_mazurok/oujctpz8/56/

Mac系统的Finder使用相同的方法,先尝试剥离左侧部分,然后再尝试右侧部分。因此,它可能会有WWWW...WWWWW,就像我的解决方案一样。

但这不是最有效率的方法。也许可以使用虚拟DOM或画布来更好地优化性能。


1
一种不考虑容器,仅关注文本元素的改进:https://jsfiddle.net/AmitBecken/sva5teym/2/。当文本元素与其他可能影响其宽度的元素相邻时非常有用。 - Amit Beckenstein
省略号是对“显示”而非内容的修改,因此屏幕阅读器不应该朗读出你修改后的textContent,其中中间部分被省略号替代。请参考我的答案,了解如何调整你所做的工作,以更新span.style.textOverflow - EoghanM

3

使用CSS无法做到这一点。问题在于HTML和CSS应该在各种浏览器和字体中工作,几乎不可能以一致的方式计算字符串的宽度。这是一个想法,可能会对您有所帮助。但是,您需要多次尝试,直到找到适当宽度的字符串。


1
我对您对此的看法很感兴趣:https://dev59.com/l3RA5IYBdhLWcg3wyA_1#36470401 - extempl

2
另一个尝试:
function truncate( str, max, sep ) {
    max = max || 10;
    var len = str.length;
    if(len > max){
        sep = sep || "...";
        var seplen = sep.length;
        if(seplen > max) { 
            return str.substring(len - max)
        }
        var n = -0.5 * (max - len - seplen);
        var center = len/2;
        return str.substring(0, center - n) + sep + str.substring(len - center + n);
    }
    return str;
}

console.log( truncate("123456789abcde") ); // 123...bcde (using built-in defaults) 
console.log( truncate("123456789abcde", 8) ); // 12...cde (max of 8 characters) 
console.log( truncate("123456789abcde", 12, "_") ); // 12345_9abcde (customize the separator) 

这是一个可爱的函数。然而,在2022年,substr已被弃用。现在建议使用substring - NetOperator Wibby

2

这是我能找到的最短的代码,它可以将中间的3个字符替换为....

function shorten(s, max) {
  return s.length > max ? s.substring(0, (max / 2) - 1) + '...' + s.substring(s.length - (max / 2) + 2, s.length) : s
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接