CSS 實現多行文本「展開收起」

本文做者:嚴文彬javascript

原創聲明:本文爲閱文前端團隊 YFE 成員出品,請尊重原創,轉載請聯繫公衆號 (id: yuewen_YFE) 獲取受權,並註明做者、出處和連接。css

多行文本展開收起是一個很常見的交互, 以下圖演示html

展開3 (1).gif 實現這一類佈局和交互難點主要有如下幾點前端

  • 位於多行文本右下角的「展開收起」按鈕
  • 「展開」和「收起」兩種狀態的切換
  • 當文本不超過指定行數時,不顯示「展開收起」按鈕

說實話,以前單獨看這個佈局,即便藉助 JavaScript 也不是一件容易的事啊(須要計算文字寬度動態截取文本,vue-clamp就是這麼作的),更別說下面的交互和判斷邏輯了,不過通過個人一番琢磨,其實純 CSS 也能完美實現的,下面就一步一步來看看如何實現吧~vue

1、位於右下角的「展開收起」按鈕

不少設計同窗都喜歡這樣的設計,把按鈕放在右下角,和文本 混合 在一塊兒,而不是單獨一行,視覺上可能更加溫馨美觀。先看看多行文本截斷吧,這個比較簡單java

1. 多行文本截斷

假設有這樣一個 html 結構git

<div class="text">
  浮動元素是如何定位的
正如咱們前面提到的那樣,當一個元素浮動以後,它會被移出正常的文檔流,而後向左或者向右平移,一直平移直到碰到了所處的容器的邊框,或者碰到另一個浮動的元素。
</div>
複製代碼

多行文本超出省略你們應該很熟悉這個了吧,主要用到用到 line-clamp,關鍵樣式以下github

.text {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
複製代碼

image.png

2. 右下角環繞效果

提到 文本環繞效果,通常能想到 浮動 float,沒錯,千萬不要覺得浮動已是過去式了,具體的場景仍是頗有用的。好比下面放一個按鈕,而後設置浮動web

<div class="text">
  <button class="btn">展開</button>
  浮動元素是如何定位的
正如咱們前面提到的那樣,當一個元素浮動以後,它會被移出正常的文檔流,而後向左或者向右平移,一直平移直到碰到了所處的容器的邊框,或者碰到另一個浮動的元素。
</div>
複製代碼
.btn {
  float: left;
  /*其餘裝飾樣式*/
}
複製代碼

image.png 若是設置右浮動瀏覽器

.btn {
  float: right;
  /*其餘裝飾樣式*/
}
複製代碼

image.png 這時已經有了 環繞 的效果了,只是位於右上角,如何將按鈕移到右下角呢?先嚐試一下 margin

.btn {
  float: right;
  margin-top: 50px;
  /*其餘裝飾樣式*/
}
複製代碼

image.png

能夠看到,雖然按鈕到了右下角,可是文本卻沒有環繞按鈕上方的空間,空出了一大截,無能爲力了嗎?

雖然 margin 不能解決問題,可是整個文本仍是受到了浮動按鈕的影響,若是有多個浮動元素會怎麼樣呢?這裏用僞元素來 ::before 代替

.text::before{
  content: '';
  float: right;
  width: 10px;
  height: 50px;/*先隨便設置一個高度*/
  background: red
}
複製代碼

image.png

如今按鈕到了僞元素的左側,如何移到下面呢?很簡單,清除一下浮動 clear: both; 就能夠了

.btn {
  float: right;
  clear: both;
  /*其餘裝飾樣式*/
}
複製代碼

image.png

能夠看到,如今文本是徹底環繞在右側的兩個浮動元素了,只要把紅色背景的僞元素寬度設置爲0(或者不設置寬度,默認就是 0),就實現了右下角環繞的效果

.text::before{
  content: '';
  float: right;
  width: 0; /*設置爲0,或者不設置寬度*/
  height: 50px;/*先隨便設置一個高度*/
}
複製代碼

image.png

3. 動態高度

上面雖然完成了右下加環繞,可是高度是固定的,如何動態設置呢?這裏能夠用到 calc 計算,用整個容器高度減去按鈕的高度便可,以下

.text::before{
  content: '';
  float: right;
  width: 0;
  height: calc(100% - 24px);
}
複製代碼

image.png

很惋惜,好像並無什麼效果,打開控制檯看看,結果發現 calc(100% - 24px) 計算高度爲 0

image.png

緣由其實很容易想到,就是高度 100% 失效的問題,關於這類問題網上的分析有不少,一般的解決方式是給父級指定一個高度,可是這裏的高度是動態變化的,並且還有展開狀態,高度更是不可預知,因此設置高度不可取。

除此以外,其實還有另外一種方式,那就是利用 flex 佈局。大概的方法就是在 flex 佈局 的子項中,能夠經過百分比來計算變化高度,具體可參考 w3.org 中關於 css-flexbox 的描述

If the flex item hasalign-self: stretch, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved.

所以,這裏須要給 .text 包裹一層,而後設置 display: flex

<div class="wrap">
  <div class="text">
    <button class="btn">展開</button>
    浮動元素是如何定位的
  正如咱們前面提到的那樣,當一個元素浮動以後,它會被移出正常的文檔流,而後向左或者向右平移,一直平移直到碰到了所處的容器的邊框,或者碰到另一個浮動的元素。
  </div>
</div>
複製代碼
.wrap{
  display: flex;
}
複製代碼

實踐下來,display: grid 和 display: -webkit-box 一樣有效,原理相似

這樣下來,剛纔的計算高度就生效了,改變文本的行數,一樣位於右下角~

image.png

除此以外,動態高度也能夠採用負的 margin 來實現(性能會比 calc 略好一點)

.text::before{
  content: '';
  float: right;
  width: 0;
  /*height: calc(100% - 24px);*/
  height: 100%;
  margin-bottom: -24px;
}
複製代碼

到這裏,右下角環繞的效果就基本完成,省略號也是位於展開按鈕以前的,完整代碼能夠查看 codepen 右下角多行展開環繞效果

4.其餘瀏覽器的兼容處理

上面的實現是最完美的處理方式。本來覺得兼容性沒什麼大問題的,畢竟只用到了文本截斷和浮動,-webkit-line-clamp 雖然是 -webkit- 前綴,不過 firefox 也是支持的,打開一看傻了眼,safarifirefox 竟然全亂了!

image.png

這就有點難受了,前面那麼多努力都白費了嗎?不可能無論這兩個,否則就只能是 demo 了,沒法用於生產環境。

趕忙打開控制檯看看是什麼緣由。一番查找,結果發現是display: -webkit-box!設置該屬性後,本來的文本好像變成了一整塊,浮動元素也沒法產生環繞效果,去掉以後浮動就正常了

image.png

那麼問題來了:沒有 display: -webkit-box 怎麼實現多行截斷呢 ?

其實上面的努力已經實現了右下角環繞的效果,若是在知道行數的狀況下設置一個最大高度,是否是也完成了多行截斷呢?爲了便於設置高度,能夠添加一個行高 line-height,若是須要設置成 3 行,那高度就設置成line-height * 3

.text {
  /* display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; */
  line-height: 1.5;
  max-height: 4.5em;
  overflow: hidden;
}
複製代碼

爲了方便更好的控制行數,這裏能夠把經常使用的行數經過屬性選擇器獨立出來(一般不會太多),以下

[line-clamp="1"] {
  max-height: 1.5em;
}
[line-clamp="2"] {
  max-height: 3em;
}
[line-clamp="3"] {
  max-height: 4.5em;
}
...
複製代碼
<!--3行-->
<div class="text" line-clamp="3">
...
</div>
<!--5行-->
<div class="text" line-clamp="5">
 ...
</div>
複製代碼

image.png

能夠看到基本上正常了,除了沒有省略號,如今加上省略號吧,跟在展開按鈕以前就能夠了,能夠用僞元素實現

.btn::before{
  content: '...';
  position: absolute;
  left: -10px;
  color: #333;
  transform: translateX(-100%)
}
複製代碼

image.png

這樣,SafariFirefox 的兼容佈局基本上就完成了,完整代碼能夠查看 codepen 右下角多行展開環繞效果(全兼容)

2、「展開」和「收起」兩種狀態

提到 CSS 狀態切換,你們都能想到 input type="checkbox" 吧。這裏咱們也須要用到這個特性,首先加一個 input,而後把以前的 button 換成 label ,而且經過 for 屬性關聯起來

<div class="wrap">
  <input type="checkbox" id="exp">
  <div class="text">
    <label class="btn" for="exp">展開</label>
    浮動元素是如何定位的
  正如咱們前面提到的那樣,當一個元素浮動以後,它會被移出正常的文檔流,而後向左或者向右平移,一直平移直到碰到了所處的容器的邊框,或者碰到另一個浮動的元素。
  </div>
</div>
複製代碼

這樣,在點擊 label 的時候,其實是點擊了 input 元素,如今來添加兩種狀態,分別是隻顯示 3 行和不作行數限制

.exp:checked+.text{
  -webkit-line-clamp: 999; /*設置一個足夠大的行數就能夠了*/
}
複製代碼

兼容版本能夠直接設置最大高度 max-height 爲一個較大的值,或者直接設置爲 none

.exp:checked+.text{
  max-height: none;
}
複製代碼

展開2.gif

這裏還有一個小問題,「展開」按鈕在點擊後應該變成「收起」,如何修改呢?

有一個技巧,凡是碰到須要動態修改內容的,均可以使用僞類 content 生成技術,具體作法就是去除或者隱藏按鈕裏面的文字,採用僞元素生成

<label class="btn" for="exp"></label><!--去除按鈕文字-->
複製代碼
.btn::after{
  content:'展開' /*採用content生成*/
}
複製代碼

添加 :checked 狀態

.exp:checked+.text .btn::after{
  content:'收起'
}
複製代碼

兼容版本因爲前面的省略號是模擬出來的,不能自動隱藏,因此須要額外來處理

.exp:checked+.text .btn::before {
    visibility: hidden; /*在展開狀態下隱藏省略號*/
}
複製代碼

展開3.gif

基本和本文開頭的效果一致了,完整代碼能夠查看 codepen 多行展開收起交互,兼容版本能夠查看 codepen 多行展開收起交互(全兼容)

還有一點,若是給max-height設置一個合適的值,注意,是合適的值,具體原理能夠參考 CSS 奇技淫巧:動態高度過渡動畫,還能加上過渡動畫

.text{
  transition: .3s max-height;
}
.exp:checked+.text{
  max-height: 200px; /*超出最大行高度就能夠了*/
}
複製代碼

Kapture 2021-05-11 at 14.21.55.gif

3、文本行數的判斷

上面的交互已經基本知足要求了,可是仍是會有問題。好比當文本較少時,此時是沒有發生截斷,也就是沒有省略號的,可是「展開」按鈕卻仍然位於右下角,如何隱藏呢?

image.png

一般 js 的解決方式很容易,比較一下元素的 scrollHeightclientHeight 便可,而後添加相對應的類名。下面是僞代碼

if (el.scrollHeight > el.clientHeight) {
  // 文本超出了
  el.classList.add('trunk')
} 
複製代碼

那麼,CSS 如何實現這類判斷呢?

能夠確定的是,CSS 是沒有這類邏輯判斷,大多數咱們都須要從別的角度,採用 「障眼法」 來實現。好比在這個場景,當沒有發生截斷的時候,表示文本徹底可見了,這時,若是在文本末尾添加一個元素(紅色小方塊),爲了避免影響原有佈局,這裏設置了絕對定位

.text::after {
    content: '';
    width: 10px;
    height: 10px;
    position: absolute;
    background: red;
}
複製代碼

展開4.gif 能夠看到,這裏的紅色小方塊是徹底跟隨省略號的。當省略號出現時,紅色小方塊一定消失,由於已經被擠下去了,這裏把父級 overflow: hidden 暫時隱藏就能看到是什麼原理了

展開5.gif

而後,能夠把剛纔這個紅色的小方塊設置一個足夠大的尺寸,好比 100% * 100%

.text::after {
    content: '';
    width: 100%;
    height: 100%;
    position: absolute;
    background: red;
}
複製代碼

9202761e575d8627136547a61fc60edc.gif

能夠看到,紅色的塊塊把右下角的都覆蓋了,如今把背景改成白色(和父級同底色),父級 overflow: hidden從新加上

.text::after {
    content: '';
    width: 100%;
    height: 100%;
    position: absolute;
    background: #fff;
}
複製代碼

展開7.gif 如今看看點擊展開的效果吧

展開8.gif

如今展開之後,發現按鈕不見(被剛纔那個僞元素所覆蓋,而且也點擊不了),若是但願點擊之後仍然可見呢?添加一下 :checked 狀態便可,在展開時隱藏覆蓋層

.exp:checked+.text::after{
    visibility: hidden;
}
複製代碼

這樣,就實現了在文字較少的狀況下隱藏展開按鈕的功能

展開9.gif

最終完整代碼能夠查看codepen 多行展開收起自動隱藏,兼容版本能夠查看codepen 多行展開收起自動隱藏(全兼容)

須要注意的是,兼容版本能夠支持到 IE 10+(這就過度了啊,竟然還支持 IE),可是因爲 IE 不支持 codepen,因此測試 IE 能夠自行復制在本地測試。

cc602bca-3538-466e-8dce-ee630ddd2635.gif

4、總結和說明

總的來講,重點仍是在佈局方面,交互其實相對容易,總體實現的成本實際上是很低的,也沒有比較生僻的屬性,除了佈局方面 -webkit-box 貌似有點 bug (畢竟是 -webkit-內核 , 火狐只是借鑑了過來,不免有些問題),幸運的是能夠經過另外一種方式實現多行文本截斷效果,兼容性至關不錯,基本上全兼容(IE10+),這裏整理一下實現重點

  • 文本環繞效果首先考慮浮動 float
  • flex 佈局子元素能夠經過百分比計算高度
  • 多行文本截斷還能夠結合文本環繞效果用max-height模擬實現
  • 狀態切換能夠藉助 checkbox
  • CSS 改變文本能夠採用僞元素生成
  • 多利用 CSS 遮擋 「障眼法」

多行文本展開收起效果能夠說是業界一個老大難的問題了,有不少 js 解決方案,可是感受都不是很完美,但願這個全新思路的 CSS 解決方式能給各位帶來不同的啓發,感謝閱讀,歡迎點贊、收藏、轉發~

相關文章
相關標籤/搜索