在 CSS 中,存在許多數學函數,這些函數能夠通過簡單的計算操作來生成某些屬性值,例如在現代 CSS 解決方案:CSS 數學函數一文中,我們詳細介紹了
- calc():用於計算任意長度、百分比或數值型數據,並將其作爲 CSS 屬性值。
- min() 和 max():用於比較一組數值中的最大值或最小值,也可以與任意長度、百分比或數值型數據一同使用。
- clamp():用於將屬性值限制在一個範圍內,支持三個參數:最小值、推薦值和最大值。
在 現代 CSS 解決方案:CSS 原生支持的三角函數 一文中,給大家介紹了從 Chrome 111 開始也逐漸開始原生支持的三角函數:
- sin()
- cos()
- tan()
而本文,我們將介紹另外一個非常有意思的數學函數 - round()。
何爲 round()?
簡單來說,round() CSS 函數的作用就是根據選定的舍入策略返回舍入數。
舉個例子,在 JavaScript 中,我們可以使用 Math.round()
返回一個數字四捨五入後最接近的整數。
譬如:
x = Math.round(20.49); //20
x = Math.round(20.5); //21
x = Math.round(-20.5); //-20
x = Math.round(-20.51); //-21
現在,CSS 藉助 round() 函數也有了相同的能力:
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 四捨五入後,最後的計算值是 2。
round() 完整語法
round() 的完整語法規則還是比較複雜的。完整的介紹可以看 MDN - round()。
使用它,可以完美實現類似於 JavaScript 中的如下幾個方法:
它的完整語法規則:
<round()> = round( <rounding-strategy>?, <valueToRound> , <roundingInterval> )
可以看到,它最多可以接收 3 個參數,並且第一個參數是可選參數:
<rounding-strategy>
:可選參數,表示舍入策略。 這可能是以下值之一:up
: 相當於 JavaScript Math.ceil() 方法,將 valueToRound 向上舍入到 roundingInterval 最接近的整數倍。 這相當於 JavaScript Math.ceil() 方法。down
:將 valueToRound 向下舍入爲 roundingInterval 最接近的整數倍。 這相當於 JavaScript Math.floor() 方法。nearest
:將 valueToRound 舍入爲 roundingInterval 的最接近的整數倍,該倍數可以高於或低於該值。 如果 valueToRound 是上方和下方舍入目標之間的一半,則會向上舍入。 相當於 JavaScript Math.round()。to-zero
:將 valueToRound 舍入爲 roundingInterval 接近/接近零的最接近整數倍。 這相當於 JavaScript Math.trunc() 方法。
<valueToRound>
:需要被四捨五入的值。 必須是<number>
、<dimension>
或<percentage>
,或者解析爲這些值之一的數學表達式。<roundingInterval>
:舍入的間隔規則。 這是一個<number>
、<dimension>
或<percentage>
,或者解析爲這些值之一的數學表達式。
基於此,舉幾個例子:
<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)); /* 最終計算值:100px */
}
div.box-2 {
height: round(up, 110px, var(--rounding-interval)); /* 最終計算值:125px */
}
div.box-3 {
height: round(down, 120px, var(--rounding-interval)); /* 最終計算值:100px */
}
div.box-4 {
height: round(to-zero, 120px, var(--rounding-interval)); /* 最終計算值:100px */
}
div.box-5 {
height: round(120px, var(--rounding-interval)); /* 最終計算值:125px */
}
結果如下:
圖中背景一個格子的大小是
25px
完整的 DEMO 可以看這裏 CodePen Demo - CSS Math Function Round() Demo
round 能解決什麼問題?
OK,鋪墊了那麼久,我們下面進入實戰環節。
那麼,round()
函數在 CSS 中有什麼具體的作用嗎?能應用到什麼地方?
解決基於 transform 的模糊問題
在之前的 疑難雜症:運用 transform 導致文本模糊的現象探究 這篇文章中,我們介紹了一種基於transform 的模糊問題。
我們來回顧一下問題現象:
在我們的頁面中,經常會出現這樣的問題,一塊區域內的文本或者邊框,在展示的時候,變得特別的模糊,如下(數據經過脫敏處理):
正常而言,應該是這樣的:
emmm,可能大圖不是很明顯,我們取一細節對比,就非常直觀了:
那麼?什麼時候會觸發這種問題呢?在 Google 上,其實我們能搜到非常多類似的案例,總結而言:
- 當文本元素的某個祖先容器存在
transform: translate()
或者transform: scale()
等transform
操作時,容易出現這種問題
當然,這只是必要條件,不是充分條件。繼續深入探究,會發現,必須還得同時滿足一些其它條件:
- 元素作用了
transform: translate()
或者transform: scale()
後的計算值產生了非整數
譬如,上述案例觸發的 CSS 代碼如下:
.container {
position: absolute;
width: 1104px;
height: 475px;
top: 50%;
transform: translateY(-50%);
// ...
}
由於元素的高度爲 475px
,translateY(-50%)
等於 237.5px
,非整數,才導致了內部的字體模糊。
但是,需要注意的是,並非所有產生的非整數都會導致了內部的字體模糊。
這裏有個簡單的示意:
還是上述的例子,當高度從 477px
一直調整到 469px
的過程中,只有 477px
和 475px
導致了模糊,而 473, 471, 469
則沒有。所以,這也只是引發模糊的一個必要條件。
- 文本內容是否模糊還與屏幕有關,高清屏(dpr > 2)下不容易觸發,更多發生在普通屏幕下(dpr = 1)
在我實測的過程中還發現,這個現象基本只會發生在 dpr 爲 1 的普通屏幕下。
類似於 MAC 的高清屏幕則不太會觸發這個問題。
dpr = 物理像素 / 設備獨立像素,表示設備像素比。這個與我們通常說的視網膜屏(多倍屏,Retina屏)有關。設備像素比描述的是未縮放狀態下,物理像素和設備獨立像素的初始比例關係。
- 並非所有瀏覽器都是這個表現,基本發生在 chromium 內核。
那麼,爲何會發生這種現象?針對這個問題,沒有找到特別官方的回答,普遍的認爲是因爲:
由於瀏覽器將圖層拆分到 GPU 以進行 3D 轉換,而非整數的像素偏移,使得 Chrome 在字體渲染的時候,不是那麼的精確。
關於這個問題,感興趣的可以再看看這兩個討論:
- Chromium Bugs -- Issue 521364: Transformed text at fractional offsets is very blurry.
- Serious bug: Slick Slider turns off subpixel font rendering on the entire site in Chrome #2275
使用 round() 函數解決模糊問題
在之前,上面的這個基於 transform 的問題基本是無解的,想要不模糊,就需要替換掉 transfrom
方法。
而在有了 round()
後,我們可以通過 round()
函數,保證作用了 transform: translate()
或者 transform: scale()
後的計算值一定是正整數,從而避免模糊問題。
譬如,原本的 CSS 如下:
.container {
width: 50vw;
height: 50vh;
transform: translate(-50%, -50%);
}
此時,transform: translate()
的實際最終計算值是會出現小數的。因此,我們可以使用 round()
函數進行取整:
.container {
width: 50vw;
height: 50vh;
transform: translate(round(-50%, 1px), round(-50%, 1px));
}
我們可以使用如下 JavaScript 代碼,打印出 transform 實時的計算值。
window.addEventListener("resize", () => {
const transform = getComputedStyle(document.querySelectorAll("div")[0]).transform;
console.log("transform:", transform);
});
如果使用 transform: translate(-50%, -50%)
resize 整個頁面,可以看到如下打印值:
可以看到,此時,transform: matrix(1, 0, 0, 1, -50.5, -106.75)
的中的後兩位,其實就是 transform: translate(-50.5px, 106.75px)
,是存在小數值的。
而使用了 transform: translate(round(-50%, 1px), round(-50%, 1px))
後,將不會再出現小數值:
完整的代碼,你可以戳這裏試一試:CodePen Demo -- round() Demo
藉由 round()
函數,我們成功的解決了一直以來,Chrome 中非常棘手的一個模糊問題!
使用 round() 模擬步驟緩動動畫
round()
還有一個有趣用法。我們可以使用 round()
實現類似於 CSS Animation 中的 steps()
步驟動畫的效果。
我們來看這麼一個 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;
}
}
這裏,我們實現了這麼一個動畫效果:
我們可以利用 round()
,把一個連貫動畫,拆解成步驟動畫:
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 的倍數或者 0deg。因此,我們可以得到和使用 stpes()
步驟動畫一樣的效果:
上面使用了 round()
的動畫,和如下的動畫效果是一致的:
div {
transform: rotate(round(var(--angle), 30deg));
}
// 等同於
div {
transform: rotate(var(--angle));
animation: propertyRotate 2s infinite steps(12);
}
因此,使用 round()
,我們也可以輕鬆的實現類似如下的 Loading 動畫效果:
完整的代碼,你可以戳這裏進行了解:CodePen Demo -- CSS Math Function Round() Animation Demo
最後
好了,本文到此結束,希望本文對你有所幫助 😃
更多精彩 CSS 技術文章彙總在我的 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。
如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。