CSS 即將支援巢狀,SASS/LESS 等前處理器已無用武之地?

ChokCoco發表於2021-08-10

最近,有一則非常振奮人心的訊息,CSS 即將原生支援巢狀 -- Agenda+ to publish FPWD of Nesting,表示 CSS 巢狀規範即將進入規範的 FWPD 階段。

目前對應的規範為 -- CSS Nesting Module

隨著 CSS 自定義屬性(CSS Variable)的大規模相容,到如今 CSS 即將支援巢狀,一些前處理器的核心功能已經被 CSS 原生支援,這是否表示 SASS/LESS 等前處理器已無用武之地?即將被淘汰了?

規範的幾個階段

首先簡單介紹一下,一個規範從提出到落地,會經歷的一些階段:

  1. 編輯草案 Editor's Draft (ED)
  2. 工作草案 Working Draft (WD)
  3. 過渡-最後通告工作草案 Transition – Last Call Working Draft (LCWD)
  4. 候選推薦標準 Candidate Recommendation (CR)
  5. 過渡-建議推薦標準 Transition – Proposed Recommendations (PR)
  6. 推薦標準 Recommendation (REC)

上文說的,即將進入 FPWD,只是處於規範的第 2 個階段 WD 階段,FPWD 表示第一次公開工作草案( First Public Working Draft (FPWD))。FPWD 後面還會有數個工作草案,會處理來自 CSSWG 內部和小組外部更廣泛社會的反饋。完善規範的設計。

也就是說,目前來看,即便後面的流程順利,要等到瀏覽器大範圍實現該規範到能落地的那天還有非常長一段時間。

除此之外,我覺得 SASS\LESS 等前處理器還有一些比較有意思的功能(函式),是即便原生 CSS 支援了自定義屬性和巢狀之後依舊欠缺的,我簡單羅列羅列我的看法。

for() 迴圈函式

目前,原生 CSS 依舊不支援迴圈函式。

但是其實在前處理器中,迴圈還算是比較常用的一個功能。考慮下面這種佈局:

ul 下面有多個 li,每個 li 的高度遞增 20px,一個一個寫當然也可以,但是有了迴圈其實能極大減少工作量:

<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>

如果沒有前處理器,我們的 CSS 可能是這樣的:

ul {
display: flex;
align-items: baseline;
justify-content: space-between;
}
li {
width: 50px;
background: #000;
}
li:nth-child(1) {
height: 20px;
}
li:nth-child(2) {
height: 40px;
}
// ... 3~9
li:nth-child(10) {
height: 200px;
}

如果利用 SASS 前處理器,可以簡化成:

ul {
display: flex;
align-items: baseline;
justify-content: space-between;
}
li {
width: 50px;
background: #000;
}
@for $i from 1 through 10 {
li:nth-child(#{$i}) {
height: calc(#{$i} * 20px);
}
}

當然,除此之外,在非常多的複雜 CSS 動畫效果中,迴圈是非常非常常用的功能。

譬如一些粒子動畫,我們通常可能需要去操縱 50~100 個粒子,也就是 50~100 個 div 的樣式,甚至更多,如果沒有迴圈,一個一個去寫效率會大打折扣。

利用前處理器迴圈功能實現的一些效果展示

下面我簡單羅列一些我實現過的,運用到了 CSS 前處理器迴圈功能的動畫效果。

像上面這個使用純 CSS 實現的火焰效果,其中的火焰的動態燃燒效果。其實是通過大量的細微粒子的運動,配合濾鏡實現。

其中使用到了 SASS 的迴圈函式的片段:

@for $i from 1 to 200 {
.g-ball:nth-child(#{$i}) {
$width: #{random(50)}px;

width: $width;
height: $width;
left: calc(#{(random(70))}px - 55px);
}

.g-ball:nth-child(#{$i}) {
animation: movetop 1s linear -#{random(3000)/1000}s infinite;
}
}

嗯哼,上面的迴圈是迴圈了 200 次之多,如果真要一個一個寫,工作量還是非常巨大的。上述效果的完整程式碼,你可以戳這裡:

CodePen Demo -- CSS Candles

if() 條件語句

接下來一個就是 if() 條件語句。

其實,CSS 中有一類非常類似條件語句的寫法,也就是媒體查詢 @media 以及 特性檢測 @supports 語句,目前 CSS 中支援的類似條件選擇的一些寫法如下:

@support 條件語句

CSS @supports 通過 CSS 語法來實現特性檢測,並在內部 CSS 區塊中寫入如果特性檢測通過希望實現的 CSS 語句。

div {
position:fixed;
}

@supports (position:sticky) {
div {
position:sticky;
}
}

上述 CSS 語句的意思是如果客戶端支援 position:sticky,則採用 position:sticky,否則,就是 position:fixed

關於 CSS 特性檢測的深入講解,你可以看看我的這篇文章:深入探討 CSS 特性檢測 @supports 與 Modernizr

@media 條件語句

另外一種常見的條件語句就是媒體查詢,這個大家還是比較熟悉的。

如果當前裝置滿足一種什麼條件,則怎麼樣怎麼樣。

article {
padding: 4rem;
}
@media screen and (min-width: 900px) {
article {
padding: 1rem 3rem;
}
}

嗯,並且,上述的兩種條件語句可以互相巢狀使用:

@supports (display: flex) {
@media screen and (min-width: 900px) {
article {
display: flex;
}
}
}

不過,上述兩種畢竟不是嚴格意義上的我們期待的 if() 語句。

很久之前,社群就有聲音(css-values - if() function),提議 CSS 規範中實現 if() 條件語句,類似於這樣:

.foo {
--calc: calc(10 * (1vw + 1vh) / 2);
font-size: if(var(--calc) < 12px, 12px, var(--calc));
}

可以看到這一語句 if(var(--calc) < 12px, 12px, var(--calc)) 類似於一個三元語句,還是比較好理解的。

然而,上述的條件語句一直沒得到支援的原因,在 scss-values - if() function 可以略窺一二。

原因是 CSS 一直在儘量避免在屬性當中產生任意依賴。在 CSS 中,屬性之間本身存在一些隱式依賴,譬如 em 單位長度受到父元素的 font-size 的影響,如果作者能夠新增任意依賴關係(通過 if() 條件語句),那麼將會導致一些問題。

原文是:this, unfortunately, means we're adding arbitrary dependencies between properties, something we've avoided doing so far because it's, in general, unresolvable.
Custom properties can arbitrarily refer to each other, but they're limited in what they can do, and have a somewhat reasonable "just become invalid" behavior when we notice a cycle. Cycles are more difficult to determine for arbitrary CSS, and can happen much more easily, because there are a number of existing, implicit between-property dependencies. For example, anything that takes a length relies on font-size (due to em), and so you can't have a value in font-size that refers to a property that takes a length (so no adjusting font-size to scale with width!). We add new dependencies of this sort over time (such as adding the lh unit, which induces a dependency on line-height); if authors could add arbitrary dependencies, we'd be unable to add new implicit ones for fear of breaking existing content (by forming cycles that were previous valid and non-cyclic).

所以,CSS 中的直接 if() 語句一直沒有得到實現。

SASS 等前處理器中的 if() 語句

最後,我們來看看前處理器中對 if() 的運用,由於 SASS 等前處理器最終還是要編譯成 CSS 檔案,所以 if() 其實並不太常用。因為 SASS 中的 if() 也無法實現類似上述說的 font-size: if(var(--calc) < 12px, 12px, var(--calc)) 這種功能。

在 SASS 中,我認為最常用的 if() 可能也就是這種場景:

@mixin triangle($size, $color, $direction) {
height: 0;
width: 0;

border-color: transparent;
border-style: solid;
border-width: $size;

@if $direction == up {
border-bottom-color: $color;
} @else if $direction == right {
border-left-color: $color;
} @else if $direction == down {
border-top-color: $color;
} @else if $direction == left {
border-right-color: $color;
} @else {
@error "Unknown direction #{$direction}.";
}
}

.next {
@include triangle(5px, black, right);
}

上述程式碼是對 CSS 實現三角形的一個封裝,通過傳入的引數,實現不同方向、顏色、大小的三角形。也就是前處理器中 if() ,更多的完成一些函式功能的封裝,方便複用。

實際上述的程式碼會被編譯成:

.next {
height: 0;
width: 0;
border-color: transparent;
border-style: solid;
border-width: 5px;
border-left-color: black;
}

Random() 隨機函式

OK,接下來這個是隨機函式,是我個人在 SASS 等前處理器中最常用的一個函式。目前原生 CSS 不支援任意形式的隨機。

在 CSS 動畫效果中,非常多的因素我們不希望是一成不變的,我們希望的是,一些屬性的值的產生由我們設定一個基礎規則,一個範圍中得到,這樣每次重新整理都能產生不同的效果。

最常見的莫過於不同的顏色、不同的長度、不同的數量等等等等。

譬如下面這個使用 CSS 實現的效果:夏日夕陽圖

我們通過隨機,每次重新整理都可以得到高度/寬度不一樣,位置不一樣的 div 塊,利用隨機的特性,繪製一幅幅不一樣的效果圖:

DEMO -- 夏日夕陽圖

目前原生 CSS 不支援任意形式的隨機。使用前處理器,也只能是在編譯前編寫隨機函式,SASS 中比較常用的隨機函式的一些寫法:

$r: random(100);

random() 是 SASS 支援的一種函式,上述 $r 就能得到一個 0 ~ 100 的隨機整數。

利用 random(),就能封裝出各種隨機函式,譬如隨機顏色:

@function randomNum($max, $min: 0, $u: 1) {
@return ($min + random($max)) * $u;
}

@function randomColor() {
@return rgb(randomNum(255), randomNum(255), randomNum(255));
}

div {
background: randomColor();
}

關於原生 CSS 實現 random() 的一些思考

下面這個是社群對原生 CSS 實現 random() 函式的一些思考,感興趣的可以猛擊:

[css-values] random() function

簡單搬運其中一些比較有意思的觀點。

假設 CSS 原生實現了 random() 函式,譬如下述這個寫法:

<p class="foo">123</p>
<p class="foo">456</p>
<p class="foo">789</p>
.foo:hover {
color: rgb(random(0, 255), 0, 0);
}

假設其中 ramdom() 是原生 CSS 實現的隨機函式,有一些事情是需要被解決或者得到大家的認可的:

  1. random(0, 255) 的值在什麼時候被確定,是在每一次 CSS 解析時,還是每一次被應用觸發時?
  2. 對於上述 DEMO,3 個 .foocolor 值是否一樣?
  3. 對於反覆的 hover,取消 hover 狀態,random(0, 255) 的值是否會發生變化?

上述的問題可以歸結於如果 CSS 原生支援隨機,隨機值的持久化和更新是必須要解決的問題。總之,目前看來,未來 CSS 原生支援隨機的可能性還是很大的。

工具函式:顏色函式、數學函式

最後,我們再來看看一些有意思的工具函式。目前原生 CSS 暫時不支援一些比較複雜的顏色函式和數學函式。但是前處理器都帶有這些函式。

在我之前的一篇關於陰影的文章中 -- 你所不知道的 CSS 陰影技巧與細節,介紹過一種利用多重陰影實現立體陰影的效果,譬如我們要實現下面這個效果:

其中的陰影的顏色變化就藉助了 SASS 的顏色函式

  • fade-out 改變顏色的透明度,讓顏色更加透明
  • desaturate 改變顏色的飽和度值,讓顏色更少的飽和
@function makelongrightshadow($color) {
$val: 0px 0px $color;

@for $i from 1 through 50 {
$color: fade-out(desaturate($color, 1%), .02);
$val: #{$val}, #{$i}px #{$i}px #{$color};
}

@return $val;
}

p{
text-shadow: makelongrightshadow(hsla(14, 100%, 30%, 1));
}

當然,除了上述的兩個顏色函式,SASS 還提供了非常多類似的顏色相關的函式,可以看看這裡:Sass基礎—顏色函式

除了顏色,數學函式也是經常在 CSS 效果中會需要用到的。

我在這篇文章中 -- 在 CSS 中使用三角函式繪製曲線圖形及展示動畫,專門講了如何利用 SASS 等前處理器實現三角函式,以實現曲線線條,實現一些有意思的效果,像是這樣:

當然,目前 SASS 也不支援三角函式,但是我們可以利用 SASS function,實現一套三角函式程式碼:

@function fact($number) {
$value: 1;
@if $number>0 {
@for $i from 1 through $number {
$value: $value * $i;
}
}
@return $value;
}

@function pow($number, $exp) {
$value: 1;
@if $exp>0 {
@for $i from 1 through $exp {
$value: $value * $number;
}
}
@else if $exp < 0 {
@for $i from 1 through -$exp {
$value: $value / $number;
}
}
@return $value;
}

@function rad($angle) {
$unit: unit($angle);
$unitless: $angle / ($angle * 0 + 1);
@if $unit==deg {
$unitless: $unitless / 180 * pi();
}
@return $unitless;
}

@function pi() {
@return 3.14159265359;
}

@function sin($angle) {
$sin: 0;
$angle: rad($angle);
// Iterate a bunch of times.
@for $i from 0 through 20 {
$sin: $sin + pow(-1, $i) * pow($angle, (2 * $i + 1)) / fact(2 * $i + 1);
}
@return $sin;
}

@function cos($angle) {
$cos: 0;
$angle: rad($angle);
// Iterate a bunch of times.
@for $i from 0 through 20 {
$cos: $cos + pow(-1, $i) * pow($angle, 2 * $i) / fact(2 * $i);
}
@return $cos;
}

@function tan($angle) {
@return sin($angle) / cos($angle);
}

就目前原生 CSS 而言,在數學函式等方面其實已經做出了非常多的努力,譬如:

  • 基礎運算函式 calc()
  • 比較函式 max()min()clamp()

等相容性已經逐漸鋪開,可以開始大規模使用,而類似於

  • 指數函式 pow()sqrt()hypot()log()exp()
  • 三角函式 sin()con()tan()
  • 階梯函式 round()mod()rem()

也在規範 CSS Values and Units Module Level 4 中被提及定義,相信不久的將來也會逐漸落地。

關於社群對數學函式的一些討論,感興趣的也可以看看這裡:Mathematical Expressions

總結一下

好了,綜上總結一下,就目前而言,我覺得 SASS/LESS 等前處理器在很多方面還是有有用武之地的,在上述的一些功能原生 CSS 沒有完全落地之前,前處理器能一定程度上彌補 CSS 的不足。

並且,除去上述說的一些我個人認為比較重要有意思的功能、函式之外,前處理器其它一些核心功能,譬如 extend、mixins 等也能有效的提升開發時的效率。

所以,在未來的一段時間內,我認為前處理器還是能和 CSS 友好共存~

最後

好了,本文到此結束,希望對你有幫助 ?

更多精彩 CSS 效果可以關注我的 CSS 靈感

更多精彩 CSS 技術文章彙總在我的 Github -- iCSS ,持續更新,歡迎點個 star 訂閱收藏。

如果還有什麼疑問或者建議,可以多多交流,原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

相關文章