現代化 Web 開發技術學習分享

0%

Sass / SCSS 預處理器 - Nesting 巢狀結構與 Parent 父選擇器

前言

上一次我們已經將 SCSS 的編譯環境給建立好了,接下來讓我們正式進入到語法的章節,首先介紹的是 nesting 巢狀結構與父選擇器,巢狀結構是 Sass / SCSS 最具特色的功能之一,之前我們有提到傳統 CSS 可能會發生父對象重複撰寫的問題,為了避免汙染到其他樣式,我們必須明確地寫出父子對象的關係,搞到最後才發現浪費了許多時間,如果改使用 Sass / SCSS 中的巢狀結構語法並搭配父選擇器,不僅可解決此類問題,同時也能改善傳統樣式表可讀性低落的問題。

筆記重點

  • 巢狀結構
  • 巢狀屬性
  • 父選擇器

巢狀結構

讓我們先來回顧一下先前提到的問題:

1
2
3
4
5
6
7
8
9
10
11
.list {
display: flex;
}

.list li {
background-color: black;
}

.list li a {
color: white;
}

你不覺得這樣子很累嗎?不寫父元素又怕會汙染到其他樣式,且也不符合近年來推崇的 DRY (Don’t Repeat Your CSS)KISS (Keep It Simple Stupid) 原則,何不我們嘗試使用 SCSS 來撰寫?改寫如下:

1
2
3
4
5
6
7
8
9
10
11
.list {
display: flex;

li {
background-color: black;

a {
color: white;
}
}
}

此時的編譯結果為:

1
2
3
4
5
6
7
8
9
10
11
.list {
display: flex;
}

.list li {
background-color: black;
}

.list li a {
color: white;
}

是不是很酷?最後的結果與之前相同,但可讀性提高了不少,我們可以很明確的知道元素之間的依賴關係,日後如果更換了父元素的名稱,也不需要 Ctrl + D 累得半死做修改,直接更改父元素的名稱,之後再重新編譯一次即可,讓我們在試一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
.list {
display: flex;
justify-content: space-between;
align-items: center;

> li {
padding: 20px 0px;

a {
color: red;
}
}
}

此時的編譯結果為:

1
2
3
4
5
6
7
8
9
10
11
12
13
.list {
display: flex;
justify-content: space-between;
align-items: center;
}

.list > li {
padding: 20px 0px;
}

.list > li a {
color: red;
}

撰寫的關鍵就在於元素之間是否存有依賴關係,如果有,即把子樣式撰寫在父樣式內,就等同於子對象選擇器的撰寫目的,包含全部的 CSS 選擇器,都可以去做使用,你不用把它想得太複雜,就只是把子對象改成巢狀結構而已,這邊在做個補充,假設 a 元素內還有 span 元素的樣式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.list {
display: flex;
justify-content: space-between;
align-items: center;

> li {
padding: 20px 0px;

a {
color: red;

span {
color: blue;
}
}
}
}

此時的編譯結果為:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.list {
display: flex;
justify-content: space-between;
align-items: center;
}

.list > li {
padding: 20px 0px;
}

.list > li a {
color: red;
}

.list > li a span {
color: blue;
}

這邊就存在一個問題了,那就是階層數可能有點太多了,存在可讀性降低、可維護性降低、渲染效率變差等問題,並不是說 3 層結構就是極限,嚴格來講應該是盡量能避免階層數過多就避免,我們可針對上面做改寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.list {
display: flex;
justify-content: space-between;
align-items: center;

> li {
padding: 20px 0px;

a {
color: red;
}

span {
color: blue;
}
}
}

這樣的寫法也可以達到同樣的選染效果,前提是 a 元素的同層元素中並沒有 span 元素,在每次撰寫樣式時,盡量去思考撰寫對象真的存在必要的依賴關係嗎,避免樣式表存在不必要的需優化及效能問題。

巢狀屬性

在上面我們都是針對 CSS 選擇器做巢狀結構,這邊再補充一點,假如我們正在撰寫關於 backgroundfont 的樣式:

1
2
3
4
5
6
7
8
9
10
11
12
.bg-cover {
background-image: url('..');
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
}

.font-weight-bold {
font-size: 1em;
font-weight: bold;
font-family: Arial, Helvetica, sans-serif;
}

這樣寫確實挺正常的,但假設你是個極度追求效率的人,懶得寫這麼多的重複字樣,可以改寫如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.bg-cover {
background: {
image: url('..');
position: center center;
repeat: no-repeat;
size: cover;
}
}

.font-weight-bold {
font: {
size: 1em;
weight: bold;
family: Arial, Helvetica, sans-serif;
}
}

此時的編譯結果為:

1
2
3
4
5
6
7
8
9
10
11
12
.bg-cover {
background-image: url('..');
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
}

.font-weight-bold {
font-size: 1em;
font-weight: bold;
font-family: Arial, Helvetica, sans-serif;
}

是不是很酷?我們就只需要寫一次 backgroundfont 等字樣,後面在撰寫其相關屬性即可,這邊要記得加入冒號,以告知編譯器此為子屬性並不是子對象。

雖然說這樣看似的確更方便了,但我一般不太會這樣寫,巢狀結構確實有其存在的必要,提高了其撰寫樣式表的效率,但巢狀屬性就見仁見智了,我認為可能會發生可閱讀性降低的問題,我自己是不太習慣,各位可以自己評估看看。

父選擇器

最後我們針對巢狀結構在做個補充,前面已經提到巢狀結構所帶來的好處了,主要解決父對象名稱大量重複的問題,但在某些情況下似乎還是無可避免,如下範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.list {
display: flex;
justify-content: space-between;
align-items: center;

> li {
padding: 20px 0px;

a {
color: red;
}

a:hover {
color: blue;
}
}
}

發現問題了嗎?a 元素還是發生名稱重複的問題了,你可能會想,這一個 :hover 偽類為何不寫在 a 元素的下個階層呢?這樣即會導致 :hover 被當成子對象編譯,形成 a :hover 的無意義宣告,我們要的是 a:hover 的結果阿!此時我們就可使用 Sass / SCSS 名為父選擇器的 & 符號解決此問題,如下範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.list {
display: flex;
justify-content: space-between;
align-items: center;

> li {
padding: 20px 0px;

a {
color: red;

&:hover {
color: blue;
}
}
}
}

& 符號可把父對象連接在一起,類似字串相加的概念,被連接的對象編譯的階層就會與父對象同層,此時的編譯結果為:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.list {
display: flex;
justify-content: space-between;
align-items: center;
}

.list > li {
padding: 20px 0px;
}

.list > li a {
color: red;
}

.list > li a:hover {
color: blue;
}

大功告成!看起來代碼的簡潔性又提升了不少,讓我們在試一次:

1
2
3
4
5
6
7
8
9
10
11
.list {
display: flex;

&__item {
color: red;

&--active {
color: blue;
}
}
}

這是一個基本的 BEM 結構,這邊先不用理解 BEM 是什麼,之後會有單獨的文章做介紹,此時的編譯結果為:

1
2
3
4
5
6
7
8
9
10
11
.list {
display: flex;
}

.list__item {
color: red;
}

.list__item--active {
color: blue;
}

由於 &__item&--active 都使用了父選擇器,故最後的編譯結果就都與 .list 同階層,這應該蠻好理解的,在實務中,我也很常使用此技法來撰寫樣式,可有效提升其閱讀性,這邊要注意,前面都是將 & 放置在 CSS 選擇器之前,你也可以把它放在 CSS 選擇器之後:

1
2
3
4
5
6
7
.menu {
max-height: 0px;

.open & {
max-height: 300px;
}
}

編譯結果為:

1
2
3
4
5
6
7
.menu {
max-height: 0px;
}

.open .menu {
max-height: 300px;
}

是不是覺得很熟悉?這不就是 hamburger menu 的手法嗎?以往我們都得在額外區塊撰寫樣式,現在利用 & 即可輕鬆完成任務,且很明顯的可以看出兩者是有其關聯性的,並不像之前散落在各自的區塊上。