在 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 技術對組件進行封裝,並進行開源