多行文本展開收起是一個很常見的交互, 以下圖演示javascript
實現這一類佈局和交互難點主要有如下幾點css
說實話,以前單獨看這個佈局,即便藉助 JavaScript 也不是一件容易的事啊(須要計算文字寬度動態截取文本,vue-clamp就是這麼作的),更別說下面的交互和判斷邏輯了,不過通過個人一番琢磨,其實純 CSS 也能完美實現的,下面就一步一步來看看如何實現吧~html
不少設計同窗都喜歡這樣的設計,把按鈕放在右下角,和文本混合在一塊兒,而不是單獨一行,視覺上可能更加溫馨美觀。先看看多行文本截斷吧,這個比較簡單vue
假設有這樣一個 html 結構java
<div class="text"> 浮動元素是如何定位的 正如咱們前面提到的那樣,當一個元素浮動以後,它會被移出正常的文檔流,而後向左或者向右平移,一直平移直到碰到了所處的容器的邊框,或者碰到另一個浮動的元素。 </div>
多行文本超出省略你們應該很熟悉這個了吧,主要用到用到line-clamp,關鍵樣式以下git
.text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
提到文本環繞效果,通常能想到浮動 float,沒錯,千萬不要覺得浮動已是過去式了,具體的場景仍是頗有用的。好比下面放一個按鈕,而後設置浮動github
<div class="text"> <button class="btn">展開</button> 浮動元素是如何定位的 正如咱們前面提到的那樣,當一個元素浮動以後,它會被移出正常的文檔流,而後向左或者向右平移,一直平移直到碰到了所處的容器的邊框,或者碰到另一個浮動的元素。 </div>
.btn { float: left; /*其餘裝飾樣式*/ }
若是設置右浮動web
.btn { float: right; /*其餘裝飾樣式*/ }
這時已經有了環繞的效果了,只是位於右上角,如何將按鈕移到右下角呢?先嚐試一下margin瀏覽器
.btn { float: right; margin-top: 50px; /*其餘裝飾樣式*/ }
能夠看到,雖然按鈕到了右下角,可是文本卻沒有環繞按鈕上方的空間,空出了一大截,無能爲力了嗎?佈局
雖然margin不能解決問題,可是整個文本仍是受到了浮動按鈕的影響,若是有多個浮動元素會怎麼樣呢?這裏用僞元素來::before代替
.text::before{ content: ''; float: right; width: 10px; height: 50px;/*先隨便設置一個高度*/ background: red }
如今按鈕到了僞元素的左側,如何移到下面呢?很簡單,清除一下浮動clear: both;就能夠了
.btn { float: right; clear: both; /*其餘裝飾樣式*/ }
能夠看到,如今文本是徹底環繞在右側的兩個浮動元素了,只要把紅色背景的僞元素寬度設置爲0(或者不設置寬度,默認就是 0),就實現了右下角環繞的效果
.text::before{ content: ''; float: right; width: 0; /*設置爲0,或者不設置寬度*/ height: 50px;/*先隨便設置一個高度*/ }
上面雖然完成了右下加環繞,可是高度是固定的,如何動態設置呢?這裏能夠用到calc計算,用整個容器高度減去按鈕的高度便可,以下
.text::before{ content: ''; float: right; width: 0; height: calc(100% - 24px); }
很惋惜,好像並無什麼效果,打開控制檯看看,結果發現calc(100% - 24px)計算高度爲 0
緣由其實很容易想到,就是高度 100% 失效的問題,關於這類問題網上的分析有不少,一般的解決方式是給父級指定一個高度,可是這裏的高度是動態變化的,並且還有展開狀態,高度更是不可預知,因此設置高度不可取。
除此以外,其實還有另外一種方式,那就是利用flex 佈局。大概的方法就是在flex 佈局的子項中,能夠經過百分比來計算變化高度,具體可參考 w3.org 中關於css-flexbox的描述
If the flex item has align-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 一樣有效,原理相似
這樣下來,剛纔的計算高度就生效了,改變文本的行數,一樣位於右下角~
除此以外,動態高度也能夠採用負的 margin 來實現(性能會比 calc 略好一點)
.text::before{ content: ''; float: right; width: 0; /*height: calc(100% - 24px);*/ height: 100%; margin-bottom: -24px; }
到這裏,右下角環繞的效果就基本完成,省略號也是位於展開按鈕以前的,完整代碼能夠查看codepen 右下角多行展開環繞效果
上面的實現是最完美的處理方式。本來覺得兼容性沒什麼大問題的,畢竟只用到了文本截斷和浮動,-webkit-line-clamp雖然是-webkit-前綴,不過firefox也是支持的,打開一看傻了眼,safari和firefox竟然全亂了!
這就有點難受了,前面那麼多努力都白費了嗎?不可能無論這兩個,否則就只能是 demo 了,沒法用於生產環境。
趕忙打開控制檯看看是什麼緣由。一番查找,結果發現是display: -webkit-box!設置該屬性後,本來的文本好像變成了一整塊,浮動元素也沒法產生環繞效果,去掉以後浮動就正常了
那麼問題來了:沒有 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>
能夠看到基本上正常了,除了沒有省略號,如今加上省略號吧,跟在展開按鈕以前就能夠了,能夠用僞元素實現
.btn::before{ content: '...'; position: absolute; left: -10px; color: #333; transform: translateX(-100%) }
這樣,Safari和Firefox的兼容佈局基本上就完成了,完整代碼能夠查看codepen 右下角多行展開環繞效果(全兼容)
提到 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; }
這裏還有一個小問題,「展開」按鈕在點擊後應該變成「收起」,如何修改呢?
有一個技巧,凡是碰到須要動態修改內容的,均可以使用僞類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; /*在展開狀態下隱藏省略號*/ }
基本和本文開頭的效果一致了,完整代碼能夠查看codepen 多行展開收起交互,兼容版本能夠查看codepen 多行展開收起交互(全兼容)
還有一點,若是給max-height設置一個合適的值,注意,是合適的值,具體原理能夠參考CSS 奇技淫巧:動態高度過渡動畫,還能加上過渡動畫
.text{ transition: .3s max-height; } .exp:checked+.text{ max-height: 200px; /*超出最大行高度就能夠 }
上面的交互已經基本知足要求了,可是仍是會有問題。好比當文本較少時,此時是沒有發生截斷,也就是沒有省略號的,可是「展開」按鈕卻仍然位於右下角,如何隱藏呢?
一般 js 的解決方式很容易,比較一下元素的scrollHeight和clientHeight便可,而後添加相對應的類名。下面是僞代碼
if (el.scrollHeight > el.clientHeight) { // 文本超出了 el.classList.add('trunk') }
那麼,CSS 如何實現這類判斷呢?
能夠確定的是,CSS 是沒有這類邏輯判斷,大多數咱們都須要從別的角度,採用「障眼法」來實現。好比在這個場景,當沒有發生截斷的時候,表示文本徹底可見了,這時,若是在文本末尾添加一個元素(紅色小方塊),爲了避免影響原有佈局,這裏設置了絕對定位
.text::after { content: ''; width: 10px; height: 10px; position: absolute; background: red; }
能夠看到,這裏的紅色小方塊是徹底跟隨省略號的。當省略號出現時,紅色小方塊一定消失,由於已經被擠下去了,這裏把父級overflow: hidden暫時隱藏就能看到是什麼原理了
而後,能夠把剛纔這個紅色的小方塊設置一個足夠大的尺寸,而且下降透明度,好比100% * 100%
.text::after { content: ''; width: 100%; height: 100%; position: absolute; background: red; }
能夠看到,紅色的塊塊把右下角的都覆蓋了,如今把背景改成白色(和父級同底色),父級overflow: hidden從新加上
.text::after { content: ''; width: 100%; height: 100%; position: absolute; background: #fff; }
如今看看點擊展開的效果吧
如今展開之後,發現按鈕不見(被剛纔那個僞元素所覆蓋,而且也點擊不了),若是但願點擊之後仍然可見呢?添加一下:checked狀態便可,在展開時隱藏覆蓋層
.exp:checked+.text::after{ visibility: hidden; }
這樣,就實現了在文字較少的狀況下隱藏展開按鈕的功能
最終完整代碼能夠查看codepen 多行展開收起自動隱藏,兼容版本能夠查看codepen 多行展開收起自動隱藏(全兼容)
須要注意的是,兼容版本能夠支持到 IE 10+(這就過度了啊,竟然還支持 IE),可是因爲 IE 不支持 codepen,因此測試 IE 能夠自行復制在本地測試。
總的來講,重點仍是在佈局方面,交互其實相對容易,總體實現的成本實際上是很低的,也沒有比較生僻的屬性,除了佈局方面-webkit-box貌似有點 bug (畢竟是-webkit-內核,火狐只是借鑑了過來,不免有些問題),幸運的是能夠經過另外一種方式實現多行文本截斷效果,兼容性至關不錯,基本上全兼容(IE10+),這裏整理一下實現重點
多行文本展開收起效果能夠說是業界一個老大難的問題了,有不少 js 解決方案,可是感受都不是很完美,但願這個全新思路的 CSS 解決方式能給各位帶來不同的啓發,感謝閱讀,歡迎點贊、收藏、轉發~