歡迎關注我的公眾號:前端偵探
不得不說,CSS 計數器是個好東西。
最近在幾篇文章中都用到了 CSS 計數器,可以將 CSS 變數透過偽元素content
動態展示出來,還可以做出很多有趣的動畫。有興趣的可以先回顧一下之前的這幾篇文章:
原理其實很簡單,content
雖然本身不支援 CSS 變數直接渲染,但是可以支援counter-reset
count::before {
--percent: 50;
counter-reset: progress var(--percent);
content: counter(progress);
}
透過一次中轉,就可以讓content
也能支援CSS變數作為字元展示了
這個技巧是透過張鑫旭的這篇文章瞭解的,非常實用:小tips: 如何藉助content屬性顯示CSS var變數值
但是,這個方法有個比較遺憾的地方就是,CSS 計數器不支援真正意義上的小數,也就是如果 CSS 變數為小數的話,直接展示為 0
count::before {
--percent: 50.15;
counter-reset: progress var(--percent);
content: counter(progress);
}
那麼,如何讓content
也支援CSS變數的小數展示呢,畢竟很多情況下還是需要小數的?比如下面這個,如果支援了小數,就可以輕易的實現數字的滾動動畫
今天一起來探討一下
一、CSS 原理拆解
CSS 計數器由於特殊性,目前都是僅支援整數的,畢竟自然個數是沒有小數的(不排除以後自定義計數器可以實現)。既然這樣,可以換一種思路,從數字形態上進行拆分。比如一個小數,48.69
可以分解成整數部分48
和小數部分69
,然後再透過小數點連結起來。這樣拆分後就都是整數了, CSS 計數器也是支援的
用程式碼實現就是(便於理解,以下的一些變數都是中文命名的,實際生產不推薦)
count::before {
--整數: 48;
--小數: 69;
counter-reset: 整數計數器 var(--整數) 小數計數器 var(--小數);
content: counter(整數計數器) "." counter(小數計數器);
}
所以問題就變成了,如何將一個小數進行拆分呢?
二、CSS變數拆分成整數和小數
接著上面的問題,假設變數是--percent
,問題就是下面兩個變數--整數
和--小數
如何透過--percent
計算而來呢?
count::before {
--percent: 48.69;
--整數: 48;
--小數: 69;
counter-reset: 整數計數器 var(--整數) 小數計數器 var(--小數);
content: counter(整數計數器) "." counter(小數計數器);
}
看似很容易,但在 CSS 中好像並不怎麼好實現。
為了解決這個,需要了解一下 CSS 自定義變數的型別。型別有很多,下面羅列一下
<length>
<number>
<percentage>
<length-percentage>
<color>
<image>
<url>
<integer>
<angle>
<time>
<resolution>
<transform-function>
<custom-ident>
<transform-list>
大部分能可以看出具體的型別,我們這裡需要用到的就兩種,<number>
和<integer>
,兩者都表示數字,具體的區別在於
<number>
表示任意的數字,整數和小數都可以<integer>
表示整型數字,只能是整數,小數會認為不合法
回到這裡,預設情況下,CSS 變數可以是任意值,但是透過自定義變數@property
可以指定變數的型別,它可以對不合法的變數進行轉換。
@property - CSS(層疊樣式表) | MDN (mozilla.org)
比如,我們需要一個整數,可以這樣來定義,將syntax
屬性設定為<integer>
就可以了
@property --整數 {
syntax: "<integer>"; /*整型*/
initial-value: 0;
inherits: false;
}
這樣,這個變數會被強制轉換成整數。比如,下面給--整數
也設定成一個小數
count::before {
--percent: 48.69;
--整數: 48.69;
--小數: 69;
counter-reset: 整數 var(--整數) 小數 var(--小數);
content: counter(整數) "." counter(小數);
}
結果...
居然直接變成了 0
?
不過沒關係,需要可以配合一些 CSS 計算函式實現自動轉換,比如calc
count::before {
--percent: 48.69;
--整數: calc(48.69);/*使用 CSS 計算後可以轉換成整數*/
--小數: 69;
counter-reset: 整數 var(--整數) 小數 var(--小數);
content: counter(整數) "." counter(小數);
}
但是,這裡變成了49
,原因其實是四捨五入造成的,並不是向下取整。為了消除這種誤差,可以再減去0.5
,所以整數部分的最終實現就是
@property --整數 {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
count::before {
--percent: 48.69;
--整數: calc(var(--percent) - 0.5);
--小數: 69;
counter-reset: 整數 var(--整數) 小數 var(--小數);
content: counter(整數) "." counter(小數);
}
未來的 CSS 數學函式應該也會有 floor、ceil 這樣的,可以期待一下~
然後是小數部分,有了整數部分,小數部分就容易了,可以用整個值減去整數部分,然後乘以 100,示意如下
用程式碼實現就是
@property --小數 {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
count::before {
--percent: 48.69;
--整數: calc(var(--percent) - 0.5);
--小數: calc((var(--percent) - var(--整數)) * 100 - 0.5);
counter-reset: 整數 var(--整數) 小數 var(--小數);
content: counter(整數) "." counter(小數);
}
效果如下
後面最末位的小數由於四捨五入的關係稍微有些偏差,沒關係,可以修正一下,加上0.01
就行了。其次,還有一個問題,當小數位小於 10 的時候,計算出的結果可能是這樣
那麼,這種情況就需要動態補零了。
關於“補零”的技巧,之前在這篇文章中有過詳細介紹:CSS 也能自動補全字串?
所以,只需要在計數器後面定義一下計數器樣式decimal-leading-zero
,表示十進位制前置零,最終實現如下
count::before {
--percent: 48.69;
--整數: calc(var(--percent) - 0.5);
--小數: calc((var(--percent) - var(--整數)) * 100 - 0.5 + 0.01);
counter-reset: 整數 var(--整數) 小數 var(--小數);
content: counter(整數) "." counter(小數, decimal-leading-zero);
}
這樣整數和小數都可以用同一個變數--percent
表示出來了,完美~
三、CSS 變數動畫
有人可能會覺得,為啥要廢這麼大勁去實現這樣一個功能?用 js 直接設定不行嗎?如果僅僅是數字的變化,那當然可以,但在這裡,除了CSS 單一變數帶來更好的可維護性外, 還可以做到連 JS 也難以做到(或者說成本更高)的事情,比如過渡動畫
首先,再改進一下,很多小數都是百分比形式的,也就是0~1
範圍內,所以前面--percent
可能是這樣的值0.4869
count::before {
--percent: 0.4869;
--百分比: calc(var(--percent) * 100);
--整數: calc(var(--百分比) - 0.5);
--小數: calc((var(--百分比) - var(--整數)) * 100 - 0.5 + 0.01);
counter-reset: 整數 var(--整數) 小數 var(--小數);
content: counter(整數) "." counter(小數, decimal-leading-zero) "%";
}
效果如下
然後,我們透過 JS 讓這個數字隨機變化
count.addEventListener('click', ev => {
ev.target.style.setProperty("--percent", Math.random());
})
效果如下
但是,這樣太死板了,我們需要數字變化的時候有個動畫,可以直接透過 CSS 自定義變數實現
@property --percent {
syntax: "<number>";
initial-value: 0;
inherits: false;
}
count{
/**/
transition: --percent 1s
}
現在看看效果,非常輕鬆的就實現了數字的滾動動畫
小數部分由於是跟隨整數部分的,比如整數從1
變為3
,那麼小數部分就跟隨變化兩個迴圈。本來這個也非常符合常理,就像時鐘的秒永遠要比分要轉的快一樣,但是有人可能覺得變的太快了,有沒有辦法讓小數部分和整數部分獨立開來呢?當然也是可以的,而且非常容易,只需要給整數部分和小數部分分別設定過渡就行了
count{
/**/
transition: --整數 1s, --小數 1s;
}
現在再看看效果,和上面對比一下
這兩種效果可以自行選擇,僅僅只是過渡的不同
試想一下,如果這個效果用 JS 來實現,是不是還有點點麻煩呢?
下面是完整程式碼(不多,就這麼幾行)
@property --percent {
syntax: "<number>";
initial-value: 0;
inherits: false;
}
@property --整數 {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
@property --小數 {
syntax: "<integer>";
initial-value: 0;
inherits: false;
}
count {
--percent: 0.4512;
font-size: 60px;
font-weight: bolder;
cursor: pointer;
font-family: 'Courier New', Courier, monospace;
--百分比: calc(var(--percent) * 100);
--整數: calc(var(--百分比) - 0.5);
--小數: calc((var(--百分比) - var(--整數)) * 100 - 0.5 + 0.01);
counter-reset: 整數 var(--整數) 小數 var(--小數);
transition: --整數 1s, --小數 1s;
}
count::before {
content: counter(整數) "." counter(小數, decimal-leading-zero) "%";
}
你也可以訪問線上 demo: CSS double num(runjs.work)
RunJS,前端程式碼線上創作與分享。
四、總結和說明
以上就是全部內容了,一個還不錯的小技巧,你學會了嗎?
- CSS 變數不支援直接在
content
中渲染,但是可以藉助計數器初始化來實現 - CSS 計數器不支援小數初始化
- CSS 計數器支援小數的實現原理在於將小數拆分為整數、小數點、小數三個部分
- CSS 自定義變數可以指定變數的型別,這樣透過 CSS 數學函式可以將一個小數轉換成整數
- 小數部分可以透過減去整數部分得到
- 小數部分還需要透過
decimal-leading-zero
補全位數 - CSS 單一變數一方面可以帶來更好的可維護性,另一方面還可以更輕易地實現過渡動畫
- 藉助
@property
可以很方便的控制 CSS 變數的過渡和動畫
數字變化動畫在一些資料大屏展示的場景下還是挺實用的,有了 CSS 變數,再也不需要透過 JS 去實時計算了。不過目前相容性還不是太好,適合內部專案小範圍使用(當然直接用了不要緊,不支援的只是沒有動畫而已)。最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤
歡迎關注我的公眾號:前端偵探