css 文字折行-后端文本截断

误解

在设计产品时,由于很多产品总监和工程师没有“字符不一定等宽”的概念,所以经常给出“超过n个字符被截断显示,中文数字算作一个字符,汉字被算作两个字符”。 需要。 你知道,这里存在很多问题:

为了展现疗效,后端往往采用以西文字体族为主的字体族设置,即西文字符使用西文字体,汉字使用英文字体,这样很容易造成文本长度过长。根据字符数控制文本。 首先,非代码内容本身并不一定适合用等宽西方字体显示。 其次,即使使用等宽的西文字体,汉字也基本上不可能恰好是两倍宽。 为了满足这个要求,我们只能放弃西文字体,对西文字符使用中文字体,而是使用中意系列的几种字体(比如SimSun,在Windows下就是“宋体”)。 (丑不说,只能满足Windows的需求。)

这些要求甚至经常与单字符码宽度的概念相混淆,从而产生“长度限制为n个字节,其中英文数字按1个字节css 文字折行,汉字按2个字节”等可笑说法。 。

顺便说一下,这种“西文等宽,中文是两倍长”的要求,一般只存在于程序员的代码编辑器中。 如果你已经处于这些强迫症的后期,不想使用中易黑体,可以考虑尝试Belleve出品的Inziu。

想法和原则

对于后端来说,数据库存储的限制不应该是我们关心的问题。 看上面的“伪需求”。 我们的实际需求从视觉上来看往往有两种:“超出一定高度的截断显示”或“超出一定行数的舞台显示”。 由于实现方法的不同,虽然可以分为“单行截断”、“多行截断”、“按高度截断”。 从成本和功效的角度来看,还有“实施难度”、“效果的准确性”、“内容是否有限制”、“能否响应页面变化”等细节需要考虑。 本文无意列出各种实现的代码,只是谈谈一些相关的问题和思路。

要查看一些现有的实现,您可以阅读这些文章:

文本溢出:省略号

我认为对此没有太多可说的。 自从 Firefox7 开始支持这个 CSS 属性以来,它已经成为 99% 情况下截断单行文本的最佳选择。 实现难度几乎为零,截断效果准确,内容中还可以包含图片、链接等其他内容,并且只能在长度变化时手动响应。 嵌套一层元素的特殊情况)。 如何支持 Firefox7 以下版本? 尝试尽可能多地捕获需求。 实在想不出其他的选择。

而如果再加上其他的需求,纯CSS的方案可能就无法满足情况了。 例如,有时我们可能希望只有在文本被截断时才通过浮动层显示键盘移入后的所有文本,而有时行尾有无法截断但长度不定的内容。

预计内容长度

百度原版Tangram库在1.x版本中有一个textOverflow方法,该方法会根据给定的长度截断单行文本。 一般的做法是估计每个字符的长度,找到plus...刚好大于指定长度的边界,然后截掉后面的字符。 为了提高性能,预先估计出ASCII字符的长度(可变宽度)和汉字的长度(等宽)并缓存,其他字符则实时估计。 在估算长度时,会在指定元素中添加一个 div 元素,并继承原始元素的所有与文本布局相关的 CSS 属性。 但事实上,如果内容本来就混合了各种样式的文本,估计可能不准确(例如div:first-child,::first-letter上有样式)。 这个方案兼容了当时所有的浏览器,处理的内容基本上只能是纯文本,但其完整性也存在一定的缺陷。

同样,用scrollWidth来判断内容是否垂直溢出也是可行的,溢出时可以不断截掉末尾的内容,直到可以用省略号完整显示剩余的内容。 实现上应该比前面的方案更简单、更准确,但是前面的方案在预先估计长度后截取内容时不需要实时读取UI上的准确长度,所以性能比这些要高。

估计内容行数

要限制WebKit浏览器下显示的行数,您可以使用非标准实现-webkit-line-clamp CSS属性,这也是您所熟知的。 联通端的应用场景可能更多,桌面端很难只支持WebKit浏览器。 当CSS无法直接解决这个问题时,JavaScript如何解决这个问题呢?

从高度中减去列宽会更容易想到。 如果没有给出列宽,则需要通过getComputedStyle获取实际的列宽。 但当line-height取默认值时,估计值是正常的,该值不一定是确定值。 因此,根据line-height估算适合自己指定列宽值的场景。 例如,在Clamp.js中,假设所有浏览器的默认值都是1.2来处理正常值。 更何况可能存在图片等超过游戏高度的内容,所以高度并不是列宽除以行数。

另外,据我所知,可以比较准确地确定内容行数的方法主要有以下两种。 这种方法的特点是列宽不需要是固定值,比如中间嵌入一个图标就改变了列宽。 我们先不讨论限制高度不确定的行数是否合理(因为我们显示内容时的高度限制往往不是来自行数,而是来自高度限制),我们来看看具体的方法。

使用 Element.getClientRects()

据测试,在IE8+等现代浏览器下,该方法对于display:inline元素有一个特点:调用结果返回的DOMRectList对象的长度等于该元素渲染后的行数。这样,我们可以把需要估计行数的内容放到一个display:inline容器中(比如原来是

元素中的文本现在修改为 p>span 等结构,并且可以通过在元素上调用 elem.getClientRects().length 来获取行数。

但目前在WebKit下,存在一个疑似bug:当display:inline容器中有子元素时,getClientRects的结果会包含那些子元素的轮廓,导致计数错误。 既然规范中没有详细描述这个方法的估计逻辑,为什么会被称为疑似bug呢? 因为当一些特定的样式被添加到容器中时,估计的结果将再次符合我们的预期结果。 详细内容请参考本期和demo。

使用 Selection.modify()

这是一个非标准的 DOM 套接字,但是 WebKit 和 Gecko 都实现了它(IE/Edge 不支持)。

总体原理是:当我们将选择区域定位到某个元素的开头时,然后执行

Selection.modify('延伸',​​'前进','lineboundary');

您可以将选择范围扩展到行尾,然后使用

Selection.modify('扩展','前进','字符');

向前延伸一个字符,如果此时selection.focusNode还在容器内,并且selection.focusOffset发生了变化,则说明下一行还有内容。 重复循环即可得到指定元素的“行数”。

在浏览器兼容性方面,即使这种方法也有比较大的局限性。 它只支持 Firefox 多于 CSS 方法。 但相对于前一种方法的优点是,由于可以立即找到换行符的字符位置,因此在拦截时无需通过截断结束内容来重复重试行数。

估计内容高度

给容器指定高度后,可以通过比较scrollHeight和clientHeight,轻松测试元素内容的高度是否超出容器的范围。 如果超过规定高度,则反复剪掉尾部内容,直至不再溢出。

拦截的内容

如果内容是纯文本,则很简单,依次删除最后一个字符即可,然后检查内容是否超过长度/行数/高度限制。 如果文本较长,可以使用二分法来优化执行效率。 同时,如果对内容进行了缓存,当内容区域的长度变大时,可以根据情况重新填充之前截取的文本,从而达到类似CSS的自适应效果。

而如果内容中还有其他HTML元素,事情就没那么好处理了。 可行的办法是仍然找到剩余内容的最后一个叶子节点,如果是文本节点,则删除最后一个字符; 否则直接删除该节点。 当长度变大的时候css 文字折行,想要恢复之前的内容就没那么简单了。 首先,保留之前删除的所有元素的引用(因为其中可能存在窃听),然后可以重新填充文本,元素节点应该像之前删除的那样恢复之前的 DOM 结构。 这样,我们可能需要记录之前删除时每一步的操作,恢复时逆向执行。 理论上是可以的,但是实现起来可能会比较复杂。

总结

可以看到,基于CSS的解决方案非常准确,但在页面布局变化和浏览器层大小变化时更容易响应,但只能满足特定场景。 使用JS的解决方案有时灵活性更好,但还有很多工作要做。 而如果要处理的内容很多,使用JS可能会造成性能困难,尽管读取实际UI显示样式的socket调用成本通常比较高。

【今日陌陌公众号推荐↓】