【前端】仿 Android TextView 實現完整的文本溢出截斷省略效果

前言

在 Android 的文本組件中,有個內容過長顯示省略號的屬性 - ellipsize ,有如下選項css

  • end: 省略號在末尾
  • start: 省略號在開頭
  • middle: 省略號在中間
  • marquee: 跑馬燈效果

並經過 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

  1. 雙擊文本進行復制的時候,默認應該是拿到所有文本,且複製的內容中不含省略號(同時該實現對搜索引擎良好,由於全部文本保留)
  2. 自適應,寬度足夠的話不顯示省略號
  3. 增長省略號在兩邊的功能,適用於某些場景(後面會提到)

單行

針對塊級元素溢出內容,CSS 有一個 text-overflow 屬性,用於處理溢出內容的處理github

目前瀏覽器支持的,經過提案的值有:算法

  • clip : 直接截斷(默認值)
  • ellipsis: 採用省略號表示被截斷的文本

以上爲 Basic support 功能chrome

在未經過的草案中還將支持 <string> 類型的值, fade 以及 fade() 方法,並容許配置兩個值用於控制先後溢出內容的行爲。可是瀏覽器基本都未支持,僅火狐支持了 String value 和 雙值。詳見瀏覽器兼容性瀏覽器

[ clip | ellipsis | <string> ]{1,2}
複製代碼

基於兼容性問題,本節的實現僅基於 Basic supportbash

省略號在末尾

最多見的需求

簡單使用 text-overflow:ellipsis 便可

爲了知足需求,還須要進行其餘設置:

  1. white-space: nowrap; // 不對文本進行換行
  2. overflow: hidden; // 隱藏溢出的文本

因爲是隱藏溢出,因此雙擊複製文本的時候,拿到的是所有的文本(不含省略號)

<div style="width: 100px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">
  我和五個優秀員工,分別是xxxx
</div>
複製代碼
我和五個優秀員工,分別是xxxx

省略號在開頭

先上例子,若是看懂的話能夠直接跳過最後看結論

<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/…

www.w3.org/TR/CSS2/vis…

<div dir="rtl" style="text-align: left;width: 100px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">
  <span dir="ltr">0:這是測試文本-ss!</span>
</div>
複製代碼
0:這是測試文本-ss!

須要注意的是,在火狐中,獲得的效果是呈現的字符相對父容器靠右

省略號在兩邊

通常使用的場景是,咱們搜索到某個關鍵字,而後要居中展現該關鍵字,左右兩邊超過邊界的字符都顯示省略號

實現思路爲切分爲兩個字符串,分爲應用 省略在開頭省略在末尾 的解決方案

如何切分,又有如下兩種思路

通常採用第二種

前半字符串含關鍵字

切分爲兩個字符串,前半字符串包含關鍵字

最後用一個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>
複製代碼
12-這是一段測試文本一段測試文本!@ 右側文本。

在控制檯中修改 wrapper 的寬度,查看效果

注意如下幾點:

  • 行內元素之間會帶一個空格;代碼裏寫成一行就不會
  • 在火狐中複製,須要 ctrl+a 全選才行
  • 因爲 IFC 的緣由,後面的內聯塊會垂直偏下,經過設置 vertical-align 解決

切分時兩個字符串各佔50%

與上種思路的效果不一樣,當父元素變大的時候,左右兩邊的字符會一塊兒不斷展現

前串經過設置 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>
複製代碼
12-這是一段測試文本!@1
吱吱吱吱看的到我嗎?

省略號在中間(或某個位置)

依舊須要對字符串進行切分

先後分別應用 省略在末尾省略在開頭

<!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 寬度足夠的時候,省略號將變爲一個,樣式上不統一

12-這是一段測試文本!@1
吱吱吱吱看的到我嗎?

目前暫未找到其餘解決方案

跑馬燈效果

採用 <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>
複製代碼

This text will scroll from right to left This text will scroll from right to left

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>
複製代碼

This text will scroll from right to left This text will scroll from right to left

利用 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>
複製代碼

This text will scroll from right to left This text will scroll from right to left This text will scroll from right to left

根據需求進行配置

多行

網上也有不少解決方案,詳見 多是最全的 「文本溢出截斷省略」 方案合集,本文不作原理講解

各有優缺點,沒有完美方案

這裏以僞元素 + 定位實現多行省略的解決方案實現多行溢出省略的效果

經過 line-heightmax-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>
複製代碼

效果以下

lines

文本溢出模式

還剩下一個問題,如何判斷當前文本處於文本溢出模式。

用於外部監聽,當處於文本溢出模式,鼠標指向該組件時應該出現一個完整文本的 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 技術對組件進行封裝,並進行開源

拓展閱讀

  1. CSS深度學習 - 文本方向 direction 和 dir
  2. bidi(雙向文字)與RTL佈局總結
  3. Unicode 控制字符及其有關的雙向算法
  4. 多行文本省略近完美方案
相關文章
相關標籤/搜索