【CSS】1809- 現(xiàn)代 CSS 解決方案:數(shù)學(xué)函數(shù) Round
在 CSS 中,存在許多數(shù)學(xué)函數(shù),這些函數(shù)能夠通過簡單的計(jì)算操作來生成某些屬性值,在現(xiàn)代 CSS 解決方案:CSS 數(shù)學(xué)函數(shù)[1]一文中,我們詳細(xì)介紹了:
-
calc():用于計(jì)算任意長度、百分比或數(shù)值型數(shù)據(jù),并將其作為 CSS 屬性值。 -
min() 和 max():用于比較一組數(shù)值中的最大值或最小值,也可以與任意長度、百分比或數(shù)值型數(shù)據(jù)一同使用。 -
clamp():用于將屬性值限制在一個(gè)范圍內(nèi),支持三個(gè)參數(shù):最小值、推薦值和最大值。
在 現(xiàn)代 CSS 解決方案:CSS 原生支持的三角函數(shù)[2] 一文中,給大家介紹了從 Chrome 111 開始也逐漸開始原生支持的三角函數(shù):
-
sin() -
cos() -
tan()
而本文,我們將介紹另外一個(gè)非常有意思的數(shù)學(xué)函數(shù) - round()。
何為 round()?
簡單來說,round() CSS 函數(shù)的作用就是根據(jù)選定的舍入策略返回舍入數(shù)。
舉個(gè)例子,在 JavaScript 中,我們可以使用 Math.round() 返回一個(gè)數(shù)字四舍五入后最接近的整數(shù)。
譬如:
x = Math.round(20.49); //20
x = Math.round(20.5); //21
x = Math.round(-20.5); //-20
x = Math.round(-20.51); //-21
現(xiàn)在,CSS 借助 round() 函數(shù)也有了相同的能力:
line-height: round(2.2, 1); /* 2 */
line-height: round(14.82, 1); /* 15 */
line-height: round(5.5, 1); /* 6 */
也就是說,round(2.2, 1) 中的 2.2 四舍五入后,最后的計(jì)算值是 2。
round() 完整語法
round() 的完整語法規(guī)則還是比較復(fù)雜的。完整的介紹可以看 MDN - round()[3]。
使用它,可以完美實(shí)現(xiàn)類似于 JavaScript 中的如下幾個(gè)方法:
-
Math.ceil()[4] -
Math.floor()[5] -
Math.round()[6] -
Math.trunc()[7]
它的完整語法規(guī)則:
<round()> = round( <rounding-strategy>?, <valueToRound> , <roundingInterval> )
可以看到,它最多可以接收 3 個(gè)參數(shù),并且第一個(gè)參數(shù)是可選參數(shù):
-
<rounding-strategy>:可選參數(shù),表示舍入策略。這可能是以下值之一: -
up: 相當(dāng)于 JavaScript Math.ceil() 方法,將 valueToRound 向上舍入到 roundingInterval 最接近的整數(shù)倍。這相當(dāng)于 JavaScript Math.ceil() 方法。 -
down:將 valueToRound 向下舍入為 roundingInterval 最接近的整數(shù)倍。這相當(dāng)于 JavaScript Math.floor() 方法。 -
nearest:將 valueToRound 舍入為 roundingInterval 的最接近的整數(shù)倍,該倍數(shù)可以高于或低于該值。如果 valueToRound 是上方和下方舍入目標(biāo)之間的一半,則會(huì)向上舍入。相當(dāng)于 JavaScript Math.round()。 -
to-zero:將 valueToRound 舍入為 roundingInterval 接近/接近零的最接近整數(shù)倍。這相當(dāng)于 JavaScript Math.trunc() 方法。 -
<valueToRound>:需要被四舍五入的值。必須是<number>、<dimension>或<percentage>,或者解析為這些值之一的數(shù)學(xué)表達(dá)式。 -
<roundingInterval>:舍入的間隔規(guī)則。這是一個(gè)<number>、<dimension>或<percentage>,或者解析為這些值之一的數(shù)學(xué)表達(dá)式。
基于此,舉幾個(gè)例子:
<div class="box-1"></div>
<div class="box-2"></div>
<div class="box-3"></div>
<div class="box-4"></div>
<div class="box-5"></div>
:root {
--rounding-interval: 25px;
}
div {
width: 100px;
background: rgba(255, 100, 0, .8);
}
div.box-1 {
height: round(nearest, 110px, var(--rounding-interval)); /* 最終計(jì)算值:100px */
}
div.box-2 {
height: round(up, 110px, var(--rounding-interval)); /* 最終計(jì)算值:125px */
}
div.box-3 {
height: round(down, 120px, var(--rounding-interval)); /* 最終計(jì)算值:100px */
}
div.box-4 {
height: round(to-zero, 120px, var(--rounding-interval)); /* 最終計(jì)算值:100px */
}
div.box-5 {
height: round(120px, var(--rounding-interval)); /* 最終計(jì)算值:125px */
}
結(jié)果如下:
圖中背景一個(gè)格子的大小是
25px
完整的 DEMO 可以看這里 CodePen Demo - CSS Math Function Round() Demo[8]
round 能解決什么問題?
OK,鋪墊了那么久,我們下面進(jìn)入實(shí)戰(zhàn)環(huán)節(jié)。
那么,round() 函數(shù)在 CSS 中有什么具體的作用嗎?能應(yīng)用到什么地方?
解決基于 transform 的模糊問題
在之前的 疑難雜癥:運(yùn)用 transform 導(dǎo)致文本模糊的現(xiàn)象探究[9] 這篇文章中,我們介紹了一種基于transform 的模糊問題。
我們來回顧一下問題現(xiàn)象:
在我們的頁面中,經(jīng)常會(huì)出現(xiàn)這樣的問題,一塊區(qū)域內(nèi)的文本或者邊框,在展示的時(shí)候,變得特別的模糊,如下(數(shù)據(jù)經(jīng)過脫敏處理):
正常而言,應(yīng)該是這樣的:
emmm,可能大圖不是很明顯,我們?nèi)∫患?xì)節(jié)對(duì)比,就非常直觀了:
那么?什么時(shí)候會(huì)觸發(fā)這種問題呢?在 Google 上,其實(shí)我們能搜到非常多類似的案例,總結(jié)而言:
-
當(dāng)文本元素的某個(gè)祖先容器存在 transform: translate()或者transform: scale()等transform操作時(shí),容易出現(xiàn)這種問題
當(dāng)然,這只是必要條件,不是充分條件。繼續(xù)深入探究,會(huì)發(fā)現(xiàn),必須還得同時(shí)滿足一些其它條件:
-
元素作用了 transform: translate()或者transform: scale()后的計(jì)算值產(chǎn)生了非整數(shù)
譬如,上述案例觸發(fā)的 CSS 代碼如下:
.container {
position: absolute;
width: 1104px;
height: 475px;
top: 50%;
transform: translateY(-50%);
// ...
}
由于元素的高度為 475px,translateY(-50%) 等于 237.5px,非整數(shù),才導(dǎo)致了內(nèi)部的字體模糊。
但是,需要注意的是,并非所有產(chǎn)生的非整數(shù)都會(huì)導(dǎo)致了內(nèi)部的字體模糊。
這里有個(gè)簡單的示意:
還是上述的例子,當(dāng)高度從 477px 一直調(diào)整到 469px 的過程中,只有 477px 和 475px 導(dǎo)致了模糊,而 473, 471, 469 則沒有。所以,這也只是引發(fā)模糊的一個(gè)必要條件。
-
文本內(nèi)容是否模糊還與屏幕有關(guān),高清屏(dpr > 2)下不容易觸發(fā),更多發(fā)生在普通屏幕下(dpr = 1)
在我實(shí)測的過程中還發(fā)現(xiàn),這個(gè)現(xiàn)象基本只會(huì)發(fā)生在 dpr 為 1 的普通屏幕下。
類似于 MAC 的高清屏幕則不太會(huì)觸發(fā)這個(gè)問題。
dpr = 物理像素 / 設(shè)備獨(dú)立像素,表示設(shè)備像素比。這個(gè)與我們通常說的視網(wǎng)膜屏(多倍屏,Retina屏)有關(guān)。設(shè)備像素比描述的是未縮放狀態(tài)下,物理像素和設(shè)備獨(dú)立像素的初始比例關(guān)系。
-
并非所有瀏覽器都是這個(gè)表現(xiàn),基本發(fā)生在 chromium 內(nèi)核。
那么,為何會(huì)發(fā)生這種現(xiàn)象?針對(duì)這個(gè)問題,沒有找到特別官方的回答,普遍的認(rèn)為是因?yàn)椋?/p>
由于瀏覽器將圖層拆分到 GPU 以進(jìn)行 3D 轉(zhuǎn)換,而非整數(shù)的像素偏移,使得 Chrome 在字體渲染的時(shí)候,不是那么的精確。
關(guān)于這個(gè)問題,感興趣的可以再看看這兩個(gè)討論:
-
Chromium Bugs -- Issue 521364: Transformed text at fractional offsets is very blurry.[10] -
Serious bug: Slick Slider turns off subpixel font rendering on the entire site in Chrome #2275[11]
使用 round() 函數(shù)解決模糊問題
在之前,上面的這個(gè)基于 transform 的問題基本是無解的,想要不模糊,就需要替換掉 transfrom 方法。
而在有了 round() 后,我們可以通過 round() 函數(shù),保證作用了 transform: translate() 或者 transform: scale() 后的計(jì)算值一定是正整數(shù),從而避免模糊問題。
譬如,原本的 CSS 如下:
.container {
width: 50vw;
height: 50vh;
transform: translate(-50%, -50%);
}
此時(shí),transform: translate() 的實(shí)際最終計(jì)算值是會(huì)出現(xiàn)小數(shù)的。因此,我們可以使用 round() 函數(shù)進(jìn)行取整:
.container {
width: 50vw;
height: 50vh;
transform: translate(round(-50%, 1px), round(-50%, 1px));
}
我們可以使用如下 JavaScript 代碼,打印出 transform 實(shí)時(shí)的計(jì)算值。
window.addEventListener("resize", () => {
const transform = getComputedStyle(document.querySelectorAll("div")[0]).transform;
console.log("transform:", transform);
});
如果使用 transform: translate(-50%, -50%) resize 整個(gè)頁面,可以看到如下打印值:

可以看到,此時(shí),transform: matrix(1, 0, 0, 1, -50.5, -106.75) 的中的后兩位,其實(shí)就是 transform: translate(-50.5px, 106.75px),是存在小數(shù)值的。
而使用了 transform: translate(round(-50%, 1px), round(-50%, 1px)) 后,將不會(huì)再出現(xiàn)小數(shù)值:

完整的代碼,你可以戳這里試一試:CodePen Demo -- round() Demo[12]
借由 round() 函數(shù),我們成功的解決了一直以來,Chrome 中非常棘手的一個(gè)模糊問題!
使用 round() 模擬步驟緩動(dòng)動(dòng)畫
round() 還有一個(gè)有趣用法。我們可以使用 round() 實(shí)現(xiàn)類似于 CSS Animation 中的 steps() 步驟動(dòng)畫的效果。
我們來看這么一個(gè) DEMO:
<div></div>
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
div {
width: 200px;
height: 200px;
border-radius: 50%;
background: conic-gradient(#fc0, #fc0 15deg, transparent 15deg, transparent 30deg);
transform: rotate(var(--angle));
animation: propertyRotate 2s infinite linear;
}
@keyframes propertyRotate {
100% {
--angle: 360deg;
}
}
這里,我們實(shí)現(xiàn)了這么一個(gè)動(dòng)畫效果:
我們可以利用 round(),把一個(gè)連貫動(dòng)畫,拆解成步驟動(dòng)畫:
div {
// ...
// transform: rotate(var(--angle));
transform: rotate(round(var(--angle), 30deg));
}
上面,我們使用 transform: rotate(round(var(--angle), 30deg)) 替換了 transform: rotate(var(--angle))。
而 round(var(--angle), 30deg) 保證了其取值只能是 30deg 的倍數(shù)或者 0deg。因此,我們可以得到和使用 stpes() 步驟動(dòng)畫一樣的效果:
上面使用了 round() 的動(dòng)畫,和如下的動(dòng)畫效果是一致的:
div {
transform: rotate(round(var(--angle), 30deg));
}
// 等同于
div {
transform: rotate(var(--angle));
animation: propertyRotate 2s infinite steps(12);
}
因此,使用 round(),我們也可以輕松的實(shí)現(xiàn)類似如下的 Loading 動(dòng)畫效果:
完整的代碼,你可以戳這里進(jìn)行了解:CodePen Demo -- CSS Math Function Round() Animation Demo[13]
最后
好了,本文到此結(jié)束,希望本文對(duì)你有所幫助 :)
更多精彩 CSS 技術(shù)文章匯總在我的 Github -- iCSS[14] ,持續(xù)更新,歡迎點(diǎn)個(gè) star 訂閱收藏。
如果還有什么疑問或者建議,可以多多交流,原創(chuàng)文章,文筆有限,才疏學(xué)淺,文中若有不正之處,萬望告知。
參考資料
現(xiàn)代 CSS 解決方案:CSS 數(shù)學(xué)函數(shù): https://github.com/chokcoco/iCSS/issues/177
[2]現(xiàn)代 CSS 解決方案:CSS 原生支持的三角函數(shù): https://github.com/chokcoco/iCSS/issues/233
[3]MDN - round(): https://developer.mozilla.org/en-US/docs/Web/CSS/round
[4]Math.ceil(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
[5]Math.floor(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
[6]Math.round(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
[7]Math.trunc(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc
[8]CodePen Demo - CSS Math Function Round() Demo: https://codepen.io/Chokcoco/pen/RwERZZp
[9]疑難雜癥:運(yùn)用 transform 導(dǎo)致文本模糊的現(xiàn)象探究: https://github.com/chokcoco/iCSS/issues/160
[10]Chromium Bugs -- Issue 521364: Transformed text at fractional offsets is very blurry.: https://bugs.chromium.org/p/chromium/issues/detail?id=521364
[11]Serious bug: Slick Slider turns off subpixel font rendering on the entire site in Chrome #2275: https://github.com/kenwheeler/slick/issues/2275
[12]CodePen Demo -- round() Demo: https://codepen.io/Chokcoco/pen/poqbdZd?editors=0110
[13]CodePen Demo -- CSS Math Function Round() Animation Demo: https://codepen.io/Chokcoco/pen/gOZMxzX?editors=1100
[14]Github -- iCSS: https://github.com/chokcoco/iCSS
回復(fù)“加群”,一起學(xué)習(xí)進(jìn)步
