在 Android 的文本组件中,有个内容过长显示省略号的属性 - ellipsize ,有如下选项css
并经过 singleline/lines 等属性进行行数的约束html
<TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:singleline="true" />
复制代码
这些功能在前端中又该如何实现呢?本文将逐一进行介绍,并封装成一个 TextView 组件前端
项目已开源,详见 TextView android
实现过程当中还须要注意如下几点:git
针对块级元素溢出内容,CSS 有一个 text-overflow 属性,用于处理溢出内容的处理github
目前浏览器支持的,经过提案的值有:算法
clip
: 直接截断(默认值)ellipsis
: 采用省略号表示被截断的文本以上为 Basic support 功能chrome
在未经过的草案中还将支持 <string>
类型的值, fade 以及 fade() 方法,并容许配置两个值用于控制先后溢出内容的行为。可是浏览器基本都未支持,仅火狐支持了 String value 和 双值。详见浏览器兼容性浏览器
[ clip | ellipsis | <string> ]{1,2}
复制代码
基于兼容性问题,本节的实现仅基于 Basic supportbash
最多见的需求
简单使用 text-overflow:ellipsis
便可
为了知足需求,还须要进行其余设置:
white-space: nowrap;
// 不对文本进行换行overflow: hidden;
// 隐藏溢出的文本因为是隐藏溢出,因此双击复制文本的时候,拿到的是所有的文本(不含省略号)
<div style="width: 100px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">
我和五个优秀员工,分别是xxxx
</div>
复制代码
先上例子,若是看懂的话能够直接跳过最后看结论
<div dir="rtl">0:这是测试文本-ss!</div>
<div dir="rtl"><span dir="ltr">0:这是测试文本-ss!</span></div>
复制代码
对应在浏览器上的显示效果是什么呢?
!这是测试文本-ss:0
0:这是测试文本-ss!
复制代码
(自右向左的书写方向)
dir HTML 属性用于决定文本整体的书写方向。默认值为 ltr 表示从左到右, rtl 表示从右到左(通常用于阿拉伯语)
效果与 CSS 的 direction 属性相同
在浏览器中,字符按 html 结构中的顺序被存放到内存中,其与页面显示的顺序是不同的。页面显示的方向由 unicode-bidi 双向书写算法决定。注意:双击选中复制文本的时候,拿的是内存中的值,不受 unicode-bidi 影响
根据方向属性, Unicode 字符分为如下几种类型:
类型 | 方向 | 字符 | 效果 |
---|---|---|---|
强字符 | LTR/RTL | 英文、汉字、阿拉伯文字等 | 方向性肯定,LTR 或 RTL,和上下文无关.且可能影响其先后字符的方向性 |
弱字符 | LTR/RTL | 数字、数字相关符号 | 方向性肯定,可是不会影响先后字符的方向性 |
中性字符 | Neutral | 大部分标点符号和空格 | 方向性不肯定,由上下文环境决定其方向 |
上下文环境由强字符方向及全局书写方向决定,具体规则后面再写一篇文章 bidi 算法的讲解
一段文本中具备相同方向性的连续字符,称为方向串
所以 <div dir="rtl">0:这是测试文本-ss!</div>
包含了以下的方向串
0 ->
: <- // dir="rtl"
这是测试文本 ->
- -> // 受强字符影响
ss ->
! <- // dir="rtl"
复制代码
这是测试文本
、-
、ss
为相同方向,继续合并为一个方向串
最后就剩
0 ->
: <-
这是测试文本-ss ->
! <-
复制代码
根据 rtl 的整体书写方向,最后在页面中显示为 !这是测试文本-ss:0
,经过光标选中也可以测试其内部方向
若是文本应用了内联元素,其文本中中性字符的方向不受外层影响
对于 <div dir="rtl"><span dir="ltr">0:这是测试文本-ss!</span></div>
这个例子
span 中全部字符都是采用 ltr 的方向,对于 div 来讲,其内容又是自右向左的。
因而就能实现自右向左水平溢出,内部字符顺序保持不变的效果
根据以上特性,咱们再应用上 text-overflow:ellipsis
试试
<!DOCTYPE HTML>
<html>
<head>
<style type="text/css"> div { text-align: left; width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } </style>
</head>
<body>
<div dir="rtl"><span dir="ltr">0:这是测试文本-ss!</span></div>
</body>
</html>
复制代码
考虑到父元素宽度足够大,而文本较少时,文本会靠右显示,故在 div 上设置 text-align: left;
实验中发现,若是 span 采用的是 direction: ltr;
,还须要加上 unicode-bidi: embed;
在边界加入一些控制字符。具体原理本文再也不分析。
unicode-bidi 的相关资料能够查看如下连接
developer.mozilla.org/zh-CN/docs/…
<div dir="rtl" style="text-align: left;width: 100px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">
<span dir="ltr">0:这是测试文本-ss!</span>
</div>
复制代码
须要注意的是,在火狐中,获得的效果是呈现的字符相对父容器靠右
通常使用的场景是,咱们搜索到某个关键字,而后要居中展现该关键字,左右两边超过边界的字符都显示省略号
实现思路为切分为两个字符串,分为应用 省略在开头 和 省略在末尾 的解决方案
如何切分,又有如下两种思路
通常采用第二种
切分为两个字符串,前半字符串包含关键字
最后用一个div 包住两个字符串,该 div 宽度为前串 div 宽度+16,并应用 省略在末尾 的解决方案
注意,为了自适应,前串的宽度定义为 max-width: calc(100% - 14px);
当父 div 足够大时,前面的字串会先展现,而后再展现后面的字串
<!DOCTYPE HTML>
<html>
<head>
<style type="text/css"> .wrapper { width: 200px; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .left { display: inline-block; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: calc(100% - 14px); vertical-align: text-bottom; } </style>
</head>
<body>
<div class="wrapper">
<span dir="rtl" class="left"><span dir="ltr">12-这是一段测试文本一段测试文本!@</span></span><span class="right">右侧文本。</span>
</div>
</body>
</html>
复制代码
在控制台中修改 wrapper 的宽度,查看效果
注意如下几点:
ctrl+a
全选才行vertical-align
解决与上种思路的效果不一样,当父元素变大的时候,左右两边的字符会一块儿不断展现
前串经过设置 max-width:50%;
样式,当父元素足够大的时候,文字始终居左
若要实现文字始终居中,则改成 width:50%;
<!DOCTYPE HTML>
<html>
<head>
<style type="text/css"> .wrapper { width: 200px; } .left { display: inline-block; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .right { width: 50%; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; } </style>
</head>
<body>
<div class="wrapper">
<div class="left" dir="rtl"><span dir="ltr">12-这是一段测试文本!@1</span></div><span class="right">吱吱吱吱看的到我吗?</span>
</div>
</body>
</html>
复制代码
依旧须要对字符串进行切分
先后分别应用 省略在末尾 和 省略在开头
<!DOCTYPE HTML>
<html>
<head>
<style type="text/css"> .wrapper { width: 200px; } .left { max-width: 50%; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; } .right { display: inline-block; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; } </style>
</head>
<body>
<div class="wrapper">
<div class="left">12-这是一段测试文本!@1</div><span class="right" dir="rtl"><span dir="ltr">吱吱吱吱看的到我吗?</span></span>
</div>
</body>
</html>
复制代码
在 chrome 上显示良好,可是火狐,IE 两个省略号中会存在一个间隔,而且当 wrapper 宽度足够的时候,省略号将变为一个,样式上不统一
目前暂未找到其余解决方案
采用 <marquee>
HTML 元素实现。该元素兼容性高,并提供多种属性配置。
loop 控制滚动次数,默认值 -1 表示连续滚动
<marquee width="100" loop="-1">This text will scroll from right to left</marquee>
<marquee width="100" loop="3">This text will scroll from right to left</marquee>
复制代码
direction 控制滚动方向,可选值有 left【默认】, right, up and down
<marquee width="100" direction="left">This text will scroll from right to left</marquee>
<marquee width="100" direction="up">This text will scroll from right to left</marquee>
复制代码
利用 scrollamount/scrolldelay/truespeed
控制滚动速度
<marquee width="100" scrollamount="6" scrolldelay="30" truespeed>This text will scroll from right to left</marquee>
<marquee width="100" scrollamount="10" scrolldelay="30" truespeed>This text will scroll from right to left</marquee>
<marquee width="100" scrollamount="6" scrolldelay="80">This text will scroll from right to left</marquee>
复制代码
根据需求进行配置
网上也有不少解决方案,详见 多是最全的 “文本溢出截断省略” 方案合集,本文不作原理讲解
各有优缺点,没有完美方案
这里以伪元素 + 定位实现多行省略的解决方案实现多行溢出省略的效果
经过 line-height
和 max-height
控制行数。至于行高应该设置多少,这个应该对外提供一个属性让用户进行配置。总之,max-height = N * line-height (em)
<!DOCTYPE HTML>
<html>
<head>
<style type="text/css"> .multi-line-ellipsis { width: 200px; max-height: 2em; line-height: 1; overflow: hidden; position: relative; text-align: justify; margin-right: -1em; padding-right: 1em; } .multi-line-ellipsis::before { content: '...'; position: absolute; right: 0; bottom: 0; } .multi-line-ellipsis::after { content: ''; position: absolute; right: 0; width: 1em; height: 1em; margin-top: 0.2em; background: white; } .block-with-text:before { content: '...'; position: absolute; right: 0; bottom: 0; } </style>
</head>
<body>
<div class="multi-line-ellipsis">这是一串短字符串</div><br/>
<div class="multi-line-ellipsis">这是一串长长长长长长长长长长长字符串</div><br/>
<div class="multi-line-ellipsis">这是一串长长长长长长长长长长长字符串,后面的内容应该会被截掉了</div>
</body>
</html>
复制代码
效果以下
还剩下一个问题,如何判断当前文本处于文本溢出模式。
用于外部监听,当处于文本溢出模式,鼠标指向该组件时应该出现一个完整文本的 Tooltip 组件
那如何判断呢?
// target 为文本元素
let containerWidth = target.getBoundingClientRect().width //当前容器的宽度
let textWidth = target.scrollWidth; //当前文字(包括省略部分)的宽度
let isEllipsis = textWidth > containerWidth
复制代码
这里咱们能够用 title 属性来模拟 Tooltip
if(isEllipsis){
target.setAttribute("title","完整文本")
} else {
el.removeAttribute("title")
}
复制代码
基本上咱们已经可以在前端模拟 Android TextView 的溢出文本效果
目前提出的解决方案可以兼容大多数主流浏览器,若存在不兼容的状况,欢迎提出
若自己包含阿拉伯字符,以上操做是否还有效,还未验证
后续将采用 React Hooks 技术对组件进行封装,并进行开源