Jeff的隨手筆記

學習當一個前端工程師

0%

『新手日記』Day-22 SASS以及BEM 命名規範

『新手日記』Day-22 SASS以及BEM 命名規範

https://miro.medium.com/max/1400/1*K6h1onQgngZ67uDMWeL7Dg.jpeg

這幾天突然看到一篇命名的文章,自己心裡想來解釋一下自己所了解的但突然發現自己說不出來,因此想說找個時間來複習一下,剛好AC講到命名是跟著SASS那就一起複習吧!

CSS 的先天限制

CSS 的開發方式是「由上而下把所有的樣式設定逐條追加在 .css 文件裡」,而這種作法導致以下的問題:

語法重複 (poor reusability)

違反了程式設計裡著名的 DRY 原則 (Do not Repeat Yourself)。

可維護性不佳

重複的語法除了降低了開發速度,也降低了系統的可維護性。

可讀性不佳 (poor readability)

CSS 的本質是「樣式階層表 (stylesheet)」,而不是程式語言。

因此,它先天欠缺程式語言要素,你無法用 CSS 既有語法傳遞資訊。

CSS 預處理器

CSS 的先天限制的解決方案 — 『CSS 預處理器 (CSS Preprocessor) 』。

它本身是 CSS 語法的擴充,讓開發者能夠處理抽象邏輯,例如變數、模組、與繼承等等。目前流行的 CSS 預處理器有:

  • Sass:最早誕生的預處理器 (2007~),源於 Ruby 社群,發展成熟,普及度高,與 Less 相互影響,持續演進中。
  • Less:稍晚於 Sass,強調兼容性並且更容易上手,也擁有自己的支持社群,例如著名的 Twitter Bootstrap 採用了 Less。
  • Stylus:本質上和 Less/Sass 類似,只是更為 Node.js 社群量身打造。

在AC我學習到得是SCSS,疑這又是什麼?Sass在演變的歷史中分成了兩個分支:

  • 舊版語法稱之為 Sass indented syntax ,副檔名為 .sass 。
  • 較新版的 Sass/SCSS 是目前業界主流,SCSS 的全文是 Sassy CSS,副檔名為 .scss

對於這段歷史可以參考這篇文章

SCSS基本語法

巢狀(Nesting)

在 Sass 中,我們可以使用巢狀語法來編寫組合選擇符 (nested combinator) ,例如空白以及 > + ~ ,以達到更好的可讀性。

https://miro.medium.com/max/1400/1*kGiGCUiIoXXtOHoLvXqRJQ.png

還有一個方便的功能是使用 & 符號來代替父層

1
2
3
4
5
6
7
8
9
10
11
12
.box{
color: rgba(0, 0, 0, 1);
&:hover{
color: red;
}
}編譯後
=====>.box{
color: rgba(0, 0, 0, 1);
}
.box:hover{
color: rgba(255, 0, 0, 1);
}

變數 (variable) 與運算子 (operator) (關鍵字:$)

變數可以使我們的 css 代碼具備更佳的可維護性與可讀性 — — Sass 使用冒號 : 進行變數的指派,並以分號 ; 作為結尾。此外,我們還可以針對數值進行各種常見的計算。

https://miro.medium.com/max/1400/1*VrV86frUCBeV5Ia1QQLUFg.png

$代表著宣告變數,變數可以用來儲存值,方便重複利用。(使用變數必須連同$一起用)

繼承與覆寫(關鍵字:% @exten)

繼承可以使多個類別都享有共同的屬性 — — Sass 使用 % 來宣告類別,並使用 @extend 來執行繼承。如果要覆寫屬性,則直接宣告即可。

https://miro.medium.com/max/1400/1*w2iYCqJvDXKYkE1Z1fl5UQ.png

混入(Mixins)(關鍵字:@mixin @include)

你可以將它視作一種更彈性的繼承 (inherit) 的實作方式。在 Sass 裡,我們使用 @mixin 進行宣告,並使用 @include 使用 mixin。

https://miro.medium.com/max/1400/1*lspko4SwkdcPYMtzr_mFVw.png

檔案模組化(關鍵字:@use)

在 Sass 中,我們可以將檔案拆成多個,配合上妥善的命名,讓專案變得更容易管理。

https://miro.medium.com/max/1400/1*HJD_K7wmNuaYbGJl-4_W3Q.png

  • 開頭為底線 _ 的檔名在 Sass 中稱之為 partial,也就是一個低階的模組。
  • 我們使用 @use 來引用 partial。請注意引用名稱不包含底線,也不包含副檔名。
  • 模組有自己的命名空間 (namespace) ,使用模組中的變數時,必須給命名空間加上前綴,例如 base.$primary-color 。

引入、匯入(關鍵字:@Import)

在檔案中加入其他SCSS或CSS檔案,最後編譯時會一併編譯。

假如我想在某個SCSS檔案裡面匯入head、main、footer等scss的檔案就可以使用下面這段程式碼

1
2
3
@import "head.scss";
@import "main.scss";
@import "footer.scss";

函式(Functions)

使用 @function 的方式其實跟 JavaScript 沒甚麼兩樣,一樣會宣告 @function 並於需要時調用,可以帶入參數,需要使用@return才收的到產生的值。

但有個不同的觀念是,在 Sass 中使用 function 其最終目的會是產生一個經過計算的值,它無法動態的去改變任何結構,因為最終都會轉成 CSS 啊~~

1
2
3
4
5
6
7
8
9
$grid-width: 60px;
$gutter-width: 10px;@function grid-width($n) {
@return $n * $grid-width + ($n - 1) * $gutter-width;
}#sidebar { width: grid-width(4);}#main {width: grid-width(8);}// 轉成 CSS
#sidebar {
width: 270px;
}#main {
width: 550px;
}

Built-In Modules

@each

Sass 有兩種資料格式儲存: list 和 map。一般可以藉由預設 function 來取得單一值,例如: nth($list,2) 取得 $list 第二個索引的值。但如果今天是要跑全部的 list 或是全部的 map,就會需要 @each 這個指令

遍歷 list

1
2
3
@each $item in $list {
//some rules or conditions
}

上述語法的 $list 為要帶入的 list 資料,可以預先存成變數使用,也可以直接塞入一組 list。而 $item 則是遍歷整個 list 時每筆 item 的變數名,此名稱可以自己定義。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$list: (orange, purple, teal);
@each $item in $list {
.#{$item} {
background: $item;
}
}// 也可以直接在 @each 中直接寫 list,兩者結果相同
@each $item in orange, purple, teal {
.#{$item} {
background: $item;
}
}// 轉成 CSS
.orange {
background: orange;
}.purple {
background: purple;
}.teal {
background: teal;
}

既然可以遍歷整個 list 的資料,當然也可以搞定更複雜一點的二維 list,二維 list 的格式會像這樣:

1
$list: (puma, black, default), (sea-slug, blue, pointer), (egret, white, move)

假設有三組 list,而每筆 list 本身又包含數個值,這樣二維 list 就會成立,因此我們可以想像成是 @each 總共會遍歷三次,而且每次的遍歷都會需要三個變數來塞值,因此就會寫成這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 這邊以直接寫入 list 示範,變數會依序對應每組 list 的值
@each $animal, $color, $cursor in (puma, black, default),
(sea-slug, blue, pointer),
(egret, white, move) {
.#{$animal}-icon {
background-image: url('/images/#{$animal}.png');
border: 2px solid $color;
cursor: $cursor;
}
}// 轉成 CSS
.puma-icon {
background-image: url("/images/puma.png");
border: 2px solid black;
cursor: default;
}.sea-slug-icon {
background-image: url("/images/sea-slug.png");
border: 2px solid blue;
cursor: pointer;
}.egret-icon {
background-image: url("/images/egret.png");
border: 2px solid white;
cursor: move;
}

遍歷 map

遍歷 map 的方式會跟 list 有一點點不同,畢竟 list 是多個單一值組在一起,但 map 會是多個含有 key 跟 value 的集合。使用 @each 在 map 身上的話,等於是依序遍歷 key 與 value,因此 @each 的變數會有兩個,分別在遍歷時帶入 key 與 value。

1
2
3
4
5
6
7
8
9
10
11
12
@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
#{$header} {
font-size: $size;
}
}// 轉成 CSS
h1 {
font-size: 2em;
}h2 {
font-size: 1.5em;
}h3 {
font-size: 1.2em;
}

BEM 命名規範

BEM由Yandex團隊提出來的一種創新命名Class名稱的設計模式。

BEM的意思是區塊(Block)、元素(Element)、修飾符(Modifier),它們最大的優勢就是可以光瀏覽class名稱就能夠告訴開發者它們彼此間的依賴關係。

  • :僅作為連字符使用,表示某個塊或者某個子元素的多單詞之間的連接記號。
  • __ :雙底線用來連接塊和塊的子元素
  • _ :單底線用來描述一個塊或者塊的子元素的一種狀態

區塊 (Block)

我們在設計網站時,一定會設計幾個區塊(Block)出來,如下圖裡面有Logo block、search block、menu block等等,這時候我們就會用class命名他們區塊對應的語意

https://miro.medium.com/max/1400/1*bE_jIwS5MKId123ww7P0lQ.png

元素 (Element)

我們由menu block來舉例,看到下圖選單內有四個元素,如果這些元素設定是會綁定在這個區塊上時,就可以在區塊的class後面加上雙底線__來辨識他是該區塊底下的元素,Class就會設計為.menu__item{}。PS:除了HTML tag外,如果是一個CSS組件也可把它視為元素 (Element) 。

https://miro.medium.com/max/1310/1*Ua8o7LxN6Gnqy7HaKqeFfQ.png

修飾符(Modifier)

用於定義 Block 或 Element 的外觀、狀態或類型,修飾符本質上類似於 HTML 屬性。

以下圖舉例:

修飾符可以在運行時更改(例如: DOM 事件的反應),因為javascript會動態加入class為.menu__item — active,因為這個原因,我們可以很容易判斷這個class是屬於元素還是修飾符的設定,只要看class最後面是雙中線 — 還是雙下底線__就知道他是屬於哪一種了。

https://miro.medium.com/max/1400/1*nm0jRv64oCreUMVQKaiSag.png

BEM 命名法的好處

BEM的關鍵是,可以獲得更多的描述和更加清晰的結構,從其名字可以知道某個標記的含義。於是,通過查看HTML 代碼中的class 屬性,就能知道元素之間的關聯。

常規的命名法:

1
2
3
4
5
6
<div class="article">
<div class="body">
<button class="button-primary"></button>
<button class="button-success"></button>
</div>
</div>
  • 這種寫法從DOM 結構和類命名上可以了解每個元素的意義,但無法明確其真實的層級關係。在css 定義時,也必須依靠層級選擇器來限定約束作用域,以避免跨組件的樣式污染。

使用了BEM 命名方法的示例:

1
2
3
4
5
6
7
<div class="article">
<div class="article__body">
<div class="tag"></div>
<button class="article__button--primary"></button>
<button class="article__button--success"></button>
</div>
</div>
  • 通過BEM 命名方式,模塊層級關係簡單清晰,而且css 書寫上也不必作過多的層級選擇。

又加上SCSS的巢狀特性以及&可以連接之前父層的class後,非常適合我們使用。

在還沒接觸SCSS時,每次看自己的CSS總是覺得非常的凌亂,當自己想修改什麼東西時只能依靠註解來找到,命名更是我這個英文很差的人一個痛苦,會的單字已經不多了,還要想這些相近意思的名字。但學習到這個之後真的解決了很多的麻煩,雖然新寫法還需要熟悉,但我相信他會是我的最佳幫手!

參考資料:

https://en.bem.info/methodology/key-concepts/