純 CSS 檢測(cè)文本是否溢出

介紹一個(gè)CSS實(shí)用技巧
一直以來(lái),CSS 都無(wú)法很好的檢測(cè)出一段文本是否溢出。但這又是一個(gè)非常普遍的需求,比如多行文本展開(kāi),展開(kāi)按鈕只有在文本溢出的時(shí)候才出現(xiàn)。

時(shí)代在進(jìn)步,CSS也在不斷推出新特性,現(xiàn)在,CSS終于可以完美的解決這個(gè)問(wèn)題了,也就是可以準(zhǔn)確無(wú)誤地判斷文本是否溢出了,一起看看吧。
一、CSS 滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)
要實(shí)現(xiàn)文本溢出檢測(cè),需要用到兩個(gè)新特性。
- CSS 滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)
- CSS 樣式查詢
為什么是這兩個(gè)呢?聽(tīng)我慢慢分析。
首先我們想一想,在 JS中是如何判斷是否溢出的?很簡(jiǎn)單。
dom.scrollHeight > dom.offsetHeight;其實(shí)也就是表示這個(gè)容器是“可滾動(dòng)”的,因?yàn)闈L動(dòng)高度超過(guò)了可視高度。

回到 CSS 這里,有沒(méi)有辦法區(qū)分呢?答案就是CSS滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)。
假設(shè)有這樣一個(gè)布局,就兩段文本。
<div class="txt">
歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧。歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧。歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧。
</div>
<div class="txt">
歡迎關(guān)注前端偵探
</div>稍微修飾一下,給個(gè)高度,讓文本可以超出滾動(dòng)。
.txt{
height: 4em;
padding: 8px;
outline: 1px dashed #9747FF;
font-family: cursive;
border-radius: 4px;
}效果如下:

左邊是可以滾動(dòng)的,右邊是不能滾動(dòng)的。
現(xiàn)在,我們給左邊加一個(gè)滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà),在滾動(dòng)時(shí)慢慢改變文本的顏色。
.txt{
animation: check 1s;
animation-timeline: scroll(self);
}
@keyframes check{
to {
color: #9747FF;
}
}注意這個(gè)scroll(self),self表示監(jiān)聽(tīng)自身滾動(dòng),默認(rèn)是最近的祖先滾動(dòng)容器,效果是這樣的。

可以看到隨著滾動(dòng),左邊文本的顏色也慢慢變化了。
接著激進(jìn)一點(diǎn),我們?cè)趧?dòng)畫(huà)中把起始點(diǎn)都設(shè)置成一樣,這樣還沒(méi)開(kāi)始滾動(dòng)就自動(dòng)變色了。
@keyframes check{
from,to {
/*動(dòng)畫(huà)起始點(diǎn)設(shè)置成相同*/
color: #9747FF;
}
}效果如下:

這樣即使還沒(méi)開(kāi)始滾動(dòng),也能提前知道是否可滾動(dòng)了。
然后,我們可以設(shè)置超出隱藏,也就是讓滾動(dòng)容器“不能滾動(dòng)”。
.txt{
overflow: hidden;
}效果如下:

也就是說(shuō)這種情況下,CSS滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)仍然可以被觸發(fā)。嘗試了一下,只要不是overflow:visible,CSS都認(rèn)為是“可滾動(dòng)”的,即“溢出”狀態(tài)。
最后,我們將文本設(shè)置成超出顯示省略號(hào)。
.txt{
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}效果如下:

是不是有點(diǎn)能區(qū)分文本是否溢出了?至少目前從文本顏色可以很好判斷。
當(dāng)然,僅僅這樣是不夠,還需要更加自由,比如在超出時(shí)可以控制其他標(biāo)簽的狀態(tài),這就需要用到 CSS 樣式查詢了。
二、CSS 樣式查詢
下面介紹一下CSS樣式查詢。
@container - CSS: Cascading Style Sheets | MDN (mozilla.org)[1]
CSS 樣式查詢是容器查詢的一部分,從名稱也可以看出,它可以查詢?cè)氐臉邮剑M(jìn)而設(shè)置額外的樣式。

比如,我們要查詢顏色為紅色的容器,然后給子元素設(shè)置背景色為黑色,可以這樣。
<style>
div{
color:red;
}
@container style(color: red) {
p {
background: black;
}
}
</style>
<div>
<p>
</p>
</div>有人可能會(huì)有疑問(wèn),為啥要設(shè)置子元素,直接設(shè)置本身不好嗎?其實(shí)是為了避免沖突,假設(shè)查詢到了color:red,然后你又設(shè)置了color:yellow,那瀏覽器該如何渲染呢?有點(diǎn)死循環(huán)了。所以為了避免這種情況,所有容器查詢都只能設(shè)置子元素樣式。
不過(guò)這種寫(xiě)法目前還不支持,僅支持CSS變量的寫(xiě)法,類似于這樣。
<style>
div{
--color:red;
}
@container style(--color: red) {
p {
background: black;
}
}
</style>
<div>
<p>
</p>
</div>回到前面的例子,我們可以給文本加一個(gè)CSS變量,就叫做 --trunc吧,表示截?cái)唷?/p>
.txt{
--trunc: false;
}然后在滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)中改變這個(gè)變量。
@keyframes check{
from,to {
/*動(dòng)畫(huà)起始點(diǎn)設(shè)置成相同*/
color: #9747FF;
--trunc: true;
}
}這樣一來(lái),滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)執(zhí)行的時(shí)候,這個(gè)變量也被賦值了。
最后我們就可以查詢這個(gè)樣式,給子元素設(shè)置樣式了,這里我們就用偽元素代替。
@container style(--trunc: true) {
.txt::after{
content: '';
position: absolute;
inset: 2px;
border: 1px solid red;
}
}這段代碼表示當(dāng)查詢到--trunc: true的條件時(shí),設(shè)置相應(yīng)的樣式,這里是畫(huà)了一個(gè)紅色的邊框,效果如下:

是不是非常容易?
你也可以查看以下在線鏈接(注意兼容性,需要 Chrome 115+,以下相同):
- CSS animation-timeline + @ container style (codepen.io)[2]
- CSS animation-timeline + @ container style (juejin.cn)[3]
有了這個(gè)作為區(qū)分,可做的事情就比較多了,下面來(lái)看幾個(gè)例子。
三、CSS 多行文本展開(kāi)收起
這已經(jīng)是第四次用不同方式來(lái)實(shí)現(xiàn)這個(gè)效果了,前幾次的實(shí)現(xiàn)可以參考文章開(kāi)頭部分。
這次來(lái)看新的實(shí)現(xiàn)方式。
首先還是把之前的結(jié)構(gòu)拿過(guò)來(lái),這些結(jié)構(gòu)是為了實(shí)現(xiàn)右下角的“展開(kāi)”按鈕必不可少的。
<div class="text-wrap">
<div class="text-content">
<label class="expand"><input type="checkbox" hidden></label>
歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧。
</div>
</div>相關(guān) CSS 如下:
.text-wrap{
display: flex;
position: relative;
width: 300px;
padding: 8px;
outline: 1px dashed #9747FF;
border-radius: 4px;
line-height: 1.5;
text-align: justify;
font-family: cursive;
}
.expand{
font-size: 80%;
padding: .2em .5em;
background-color: #9747FF;
color: #fff;
border-radius: 4px;
cursor: pointer;
float: right;
clear: both;
}
.expand::after{
content: '展開(kāi)';
}
.text-content{
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.text-content::before{
content: '';
float: right;
height: calc(100% - 24px);
}
.text-wrap:has(:checked) .text-content{
-webkit-line-clamp: 999;
}
.text-wrap:has(:checked) .expand::after{
content: '收起';
}這時(shí)的效果是這樣的。

通過(guò)上一節(jié)的原理,我們通過(guò)滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)來(lái)判斷是否溢出,并使用CSS變量作為標(biāo)識(shí),然后利用樣式查詢來(lái)控制展開(kāi)按鈕的顯示狀態(tài),關(guān)鍵實(shí)現(xiàn)如下:
.expand{
/**/
display: none;
}
.text-content{
--trunc: false;
animation: check 1s;
animation-timeline: scroll(self);
}
@keyframes check{
from,to {
--trunc: true;
}
}
@container style(--trunc: true) {
.expand{
display: initial;
}
}展開(kāi)按鈕默認(rèn)是隱藏的,這樣只有在文本溢出的時(shí)候才出現(xiàn),效果如下:

效果出來(lái)了,不過(guò)在點(diǎn)擊展開(kāi)后按鈕也跟著消失了。這是因?yàn)檎归_(kāi)后,CSS檢測(cè)出這時(shí)沒(méi)有溢出,所以樣式查詢里的語(yǔ)句就不生效了,自然也就回到了之前的隱藏狀態(tài)。
要解決這個(gè)問(wèn)題也很簡(jiǎn)單,在展開(kāi)的時(shí)候始終顯示按鈕就行了,用:checked可以判斷是否展開(kāi)。
.text-wrap:has(:checked) .expand{
display: initial;
}這樣就正常了,完美!

CSS方式的好處是監(jiān)控是實(shí)時(shí)的,比如手動(dòng)改變?nèi)萜鞯膶挾?,也?huì)自動(dòng)顯示或者隱藏這個(gè)按鈕。

完整demo可以查看以下在線鏈接( Chrome 115+):
- CSS container style expand (codepen.io)[5]
- CSS container style expand (juejin.cn)[6]
四、CSS 文本超出時(shí)顯示 tooltips
還有一個(gè)比較常見(jiàn)的需求,就是希望在文本出現(xiàn)省略號(hào)時(shí),鼠標(biāo)hover有tooltips提示,就像這樣。

原理和上面幾乎一致,我們一步步來(lái)看。
首先還是結(jié)構(gòu),沒(méi)什么特別的。
<div class="txt" data-title="這是一段可以自動(dòng)出現(xiàn)tooltip的文本">
這是一段可以自動(dòng)出現(xiàn)tooltip的文本
</div>這里加了一個(gè)data-title,是用來(lái)顯示氣泡的,通過(guò)偽元素content獲取屬性內(nèi)容。
.txt{
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 8px;
outline: 1px dashed #9747FF;
font-family: cursive;
border-radius: 4px;
}
.txt::after{
content: attr(data-title);
position: absolute;
top: 0;
width: fit-content;
left: 50%;
margin: auto;
transform: translate(-50%,-100%);
background-color: rgba(0,0,0,.6);
padding: .3em 1em;
border-radius: 4px;
color: #fff;
opacity: 0;
visibility: hidden;
transition: .2s .1s;
}效果如下:

目前是沒(méi)有任何提示的。
下面加上CSS溢出檢測(cè),在檢測(cè)到溢出時(shí)hover生效。仍然是相同的代碼,添加一個(gè)滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà),然后樣式查詢。
.txt{
--trunc: false;
animation: check 1s;
animation-timeline: scroll(x self);
}
@keyframes check{
from,to {
--trunc: true;
}
}
@container style(--trunc: true) {
.txt:hover::after{
opacity: 1;
visibility: visible;
}
}注意,這里的scroll(x self),加了一個(gè)x,因?yàn)檫@時(shí)的文本是橫向溢出的,所以需要加上滾動(dòng)驅(qū)動(dòng)軸(默認(rèn)是垂直方向)。
另外,由于超出隱藏,所以tooltip需要一個(gè)新的父級(jí),不然就被裁掉了。
<div class="wrap">
<div class="txt" data-title="這是一段可以自動(dòng)出現(xiàn)tooltip的文本">
這是一段可以自動(dòng)出現(xiàn)tooltip的文本
</div>
</div>.wrap{
position: relative;
}這樣就能實(shí)現(xiàn)文本超出時(shí)顯示 tooltips。

完整demo可以查看以下在線鏈接( Chrome 115+):
- CSS container style tooltip (codepen.io)[7]
- CSS container style tooltip (juejin.cn)[8]
五、最后總結(jié)一下
CSS 就是這么神奇,將兩個(gè)幾乎不相關(guān)的特性組合起來(lái),就能實(shí)現(xiàn)完全不一樣的功能,這可是在其他語(yǔ)言中做不到的,簡(jiǎn)單回顧一下CSS檢測(cè)代碼。
.content{
--trunc: false;
animation: check 1s;
animation-timeline: scroll(x self); /*注意溢出方向*/
}
@keyframes check{
from,to {
--trunc: true; /*滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)*/
}
}
/*查詢溢出狀態(tài)*/
@container style(--trunc: true) {
}是不是非常容易,幾乎是無(wú)侵入式的,下面總結(jié)一下本文重點(diǎn)。
- 要實(shí)現(xiàn)文本溢出檢測(cè),需要用到兩個(gè)新特性,CSS滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)和CSS樣式查詢。
- CSS滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)可以檢測(cè)出容器是否可滾動(dòng),也就是溢出,即使是在超出隱藏的情況下。
- CSS樣式查詢可以查詢到CSS變量的變化,從而設(shè)置不同的樣式。
- 借助CSS滾動(dòng)驅(qū)動(dòng)動(dòng)畫(huà)和CSS樣式查詢,可以很輕松的實(shí)現(xiàn)文本溢出檢測(cè)。
- 兩個(gè)實(shí)例:CSS多行文本展開(kāi)收起和CSS文本超出時(shí)顯示 tooltips。
當(dāng)然除了以上一些案例,還可以做的事情很多,比如以前有寫(xiě)一篇判斷指定高度后就顯示折疊按鈕,也可以用這種方式來(lái)實(shí)現(xiàn),幾乎所有與溢出相關(guān)的交互都可以純CSS完成。
至于兼容性,目前僅支持 chrome 115+,還是需要多等等,多多關(guān)注,說(shuō)不定哪一天就能用上了呢,比如5年前推出的CSS scroll snap,現(xiàn)在幾乎可以愉快使用了,再也無(wú)需swiper.js這樣的庫(kù)了。
[1]@container - CSS: Cascading Style Sheets | MDN (mozilla.org): https://developer.mozilla.org/en-US/docs/Web/CSS/@container。
[2]CSS animation-timeline + @ container style (codepen.io): https://codepen.io/xboxyan/pen/jORrXBe。
[3]CSS animation-timeline + @ container style (juejin.cn): https://code.juejin.cn/pen/7346120235966267427。
[4]CSS 實(shí)現(xiàn)多行文本“展開(kāi)收起”: https://juejin.cn/post/6963904955262435336。
[5]CSS container style expand (codepen.io): https://codepen.io/xboxyan/pen/qBwaaWW。
[6]CSS container style expand (juejin.cn): https://code.juejin.cn/pen/7346120018578374694。
[7]CSS container style tooltip (codepen.io): https://codepen.io/xboxyan/pen/oNOzzYb。
[8]CSS container style tooltip (juejin.cn): https://code.juejin.cn/pen/7346125496281333814。
































