iT邦幫忙

2021 iThome 鐵人賽

DAY 2
7
Modern Web

排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!系列 第 3

Day 02:「Tailwind CSS?那好吃嗎?」- 淺談 Tailwind 的核心概念

Day02-Banner

嗨各位! 我們終於度過了昨天那篇漫長的業配文了,很快的我們就要開始進入主餐部分。

雖然你們已經把刀叉拿好了,
但是在上主餐之前,必須提醒一下剛吃完前菜的你們:

「這道主餐有淋上高濃度 CSS 辣椒熬製而成的精華醬,所以還不太能吃辣的朋友,要小心,慢慢吃就好。」

那麼客人,現在圍兜兜圍好,準備上菜囉!
 

carrotPoint Tailwind 的核心概念

了解各個核心概念的特色之前,我們先來大致看一下有哪些核心概念:

  • 功能優先 (Utility First)
  • 響應式設計 (RWD)
  • Hover, Focus 以及其他狀態
  • 深色模式 (Dark Mode)
  • 增加基底樣式
  • 提取成元件
  • 增加新功能
  • 函數與指令

如果每個都直接細講可能會太硬、難消化,所以為了方便理解,我們將核心概念分類重新調整順序後,大約是:

  • 功能優先 (Utility First)
  • 變化模式 (Variants)
    • Hover、Focus 以及其他狀態
    • 響應式設計 (RWD)
    • 深色模式
  • 客製化
    • 提取成元件
    • 函數與指令
    • 增加基底樣式和新功能

那我們一道一道的請大廚來介紹吧!
 

carrotPoint 功能優先 (Utility First)

「主餐的基底,是用功能優先牌的優質後腿...」
不是啦,是終於要來解惑了! 一直聽到 Utility 這個名詞大量出現在文章裡,是不是以為這都要變成兔兔的口頭禪了呀XD

「何謂功能性 class (Utility Class) 呢?」
「為何 Tailwind 之中完全都是使用功能性 class 呢?」

答案揭曉!Utility公用程式 (公用工具)、功能的意思。而所謂的功能性 class 與一般常見的 class 的不同之處就在於:

功能性 class 一般 class
職責 職責單純 只負責單一種樣式的呈現 職責較複雜 一個 class 可能出現多種不同用途的樣式
名稱 名稱即用途 看到名稱就知道效果 名稱較抽象 名稱通常與樣式效果無關,較冗長

 

其實不只上表所列的這些,但是這是最顯著、最可以輕鬆理解的差異。不過這樣解釋可能還是有點抽象,我們直接來看個範例。

案例 1:拔蘿蔔農場網站 LOGO

把我們拔蘿蔔農場網站的 logo 透過了傳統以及功能性 class 來各自實現,左邊就是最傳統、一般常見的寫法,右邊則是功能性 class 的寫法。不難發現傳統的寫法就是把所有需要用到的樣式全部加進一個 class 名稱裡,而 class 的名稱通常會跟物件用途比較相關。

功能性 class 雖然一路下來耗費了非常多的 class,但是可以清楚的注意到 class 名稱與樣式效果的功能相關,而且有著一定的規範。然後值得觀察一下的地方是這些 class 名稱多為縮略詞 (縮寫),看習慣之後直接閱覽 html 標籤的部分就可以想像出大概會長什麼樣,而非項傳統寫法,看到了 class 名稱卻無法在腦中與樣式互相連結,而這也是在 Day1 中所提到使用 Tailwind 的優點之一:「與 UI 設計保持視覺一致」

(P.S.「與 UI 設計保持視覺一致」 意即看到內容就可以馬上聯想到所設計的樣式)

 

案例 2:兔學院教師證

我們接下來看一下這個案例。兔老師想用網頁輕鬆設計一個固定的名片卡格式印出來,但是兩種寫法在這裡會開始產生維護上以及開發者體驗上的差異。一樣我們左邊是傳統寫法,右邊是功能性 class,仔細尋著箭頭看過來再看過去,會發現只是為了保持一樣寬高,width 和 height 重複寫了兩次,但在使用功能性 class 的 tailwind 上沒有這個問題,width 和 height 只定義了一次但卻可以被重複使用 N 次

這在你的樣式或設計系統、甚至是到網站架構開始龐大、頁面數開始變多時,傳統的方法很容易讓 CSS 撰寫量頻繁的往上增加,但 Tailwind 的 CSS 量永遠都是差不多固定的,端看你怎麼組合使用
 

案例 3:「沒有案例 3 了啦!」

其實可能有些人會覺得說:「啊我要開始設計之前還要先把那些樣式都先定義出來喔!那不累死!」

的確啦,那麼做是很扯的,因為那就完全喪失了那些優點存在的意義了。不過別忘了,Tailwind 是一個 CSS 框架。而何謂框架?簡單來說就是把你框在這裡、架在這裡,你很難逃脫出這個小圈圈。既然打算把你眷養在他的柵欄裡,總是會給你食物、給你好處的吧?

剛剛上面兩個案例中所用到的功能性 class,Tailwind 預設都有提供,你不需要定義出什麼是 w-32h-32、什麼是bg-yellow-500,又或者是 text-3xl因為這些海量的基礎樣式 Tailwind 都已經幫你準備好了,你只要用即可。

*(P.S.*讓你再也不需要耗費心力去想 class 要取什麼名稱,比起那些抽象的命名,還是使用功能性 class 會更加人性化一些。)

 

「可是兔兔,你只有解釋功能性 class,但沒說核心概念的功能優先 (Utility First) 是在優先幾點的呀!」

「丟齁~差點就忘記了。... 欸不對啊,其實剛剛都有講到啦!」

所謂的功能優先 (Utility First) 呢,我們在這邊換另一種說法:功能第一。這也就是說在我們撰寫樣式時,皆以功能性 class 為主,來取代傳統的 class 定義以及 inline style 的使用。 (除非不得已,不然請優先使用 Utility )
 

carrotPoint 變化模式 (Variants)

啊!又來了個艱深的兔兔語了! 好啦其實 Variant 就是變種 (變化體、變體),我這邊統一稱呼為變化模式

所謂的變化模式就是在特定狀態、條件下會觸發某個東西的機制。而在 Tailwind 之中,要觸發的當然就是樣式啦!這邊來圖解一下:

簡單來說,左邊的兔子是原本的樣式 rabbit-give,但當 hungry: 這個變化模式觸發 (條件成立) 時,就會套用 rabbit-eat 這個樣式,而你看到的兔子就會從左邊拿著紅蘿蔔的樣子,變成穿著圍兜兜正在吃紅蘿蔔的兔子。

「高級蘿蔔條」

經過上面一番折騰,現在應該瞭解到了功能性 class 以及它的用法,但其實還不完全,還要再加上變化模式才算完整。跟上個 part 不同,我們這邊要來比較的是 inline style功能性 class

同樣是做出上面這個登入按鈕,先來看 inline style:

<!-- inline style -->
<button style="background-color:#2463EB;color:white;padding:0.5rem 1.5rem;border-radius:0.5rem">
  登入
</button>

再來看功能性 class:

<!-- utility class -->
<button class="bg-blue-600 text-white py-2 px-6 rounded-lg m-5">
  登入
</button>

你可能會想:「我已經知道啦!它就是在裡面 html 標籤的 class 裡面寫一長~串,實際上就是在做 inline style 的概念而已啊!」 雖然你說的沒錯,但功能性 class 除了語法上較為簡潔,還能完成一件 inline style 做不到的事情,就是可以 hover

以往,我們如果要完成滑鼠懸停就可以改變樣式的這個功能,我們不會考慮寫 inline style,應該說實務上非不得已,絕不會考慮使用 inline style 的寫法,我們接下來看看加上 hover 功能該怎麼辦:

inline-style:「好啦我放棄,這關我做不到 QQ ... 我只能 call out 請救兵了! 傳統式 class,幫我完賽!」,傳統式先生使盡了渾身解數、滿頭大汗的才把 inline-style 的賽況給挽救回來。

<!-- inline style -->
<button class="login-button" style="background-color:#2463EB;color:white;padding:0.5rem 1.5rem;border-radius:0.5rem;">
  登入
</button>

<style>
  .login-button:hover {
    background-color:#61A6FA !important;
  }
</style>

但反觀 Tailwind 的功能性 class ...「什麼!! Utility 選手只花一步就華麗的完成,已經在旁邊等很久了,甚至還泡起了下午茶!!」

<!-- utility class -->
<button class="hover:bg-blue-400 bg-blue-600 text-white py-2 px-6 rounded-lg m-5">
  登入
</button>
它們的比賽結果:hover 範例

他們的賽事中有一個重點就是,你如果想做像是 hover 或是 focus、active 這些狀態觸發時有互動效果的話,傳統做法你要新增一個 class,並在選擇器後加上:{狀態},而 class 之中的樣式也需要你自己撰寫;但如果你是使用 Tailwind 的話,這些 class 的樣式都是已經定義好的,你只要在改變後的樣式前加上{狀態}:即可。

最後一個小範例,是想要把 「紅色按鈕在滑鼠懸停時變成藍色」 時,只需要這麼做: (範例連結)

<button class="bg-red hover:bg-blue ... ">
  按鈕 1
</button>

那我們接下來就可以來介紹各種變化模式啦~如果你已經不記得開頭所提到的變化模式分類下有哪些核心概念,那我這邊再列出來一起複習一下,有「Hover、Focus 以及其他狀態」、「響應式設計 (RWD)」以及目前最潮的「深色模式

(P.S. "潮" 這個詞現在是不是過時了呀)

 

Hover、Focus 以及其他狀態

其實透過剛剛的小範例,應該很能夠理解這個核心概念的用法了,而它主要的實現方法像是這個樣子:

<button class="bg-red hover:bg-blue">
  按鈕 1
</button>

<style>
.bg-red {
  background-color:red;
}

.hover\:bg-blue:hover {
  background-color:blue;
}
</style>

也就是透過\逸出字元 (或稱跳脫字元,Escape Character) 的概念,來把{狀態}:當成一個 class 名稱,並指定在 hover 狀態觸發,所以就是用 {狀態}:樣式:{狀態} 的這個結構來實現。

然後我們不會先在這邊介紹完所有的狀態變化模式,只需要先了解為什麼,關於更多狀態變化模式的操作我們在後面的幾天會談到
 

響應式設計 (RWD)

Tailwind 響應式設計的核心概念是透過變化模式來控制不同螢幕寬度時的樣式。如果你有看懂上一段,那你這段應該可以以此類推,就是那種:「哦哦哦哦靈感來了!」的感覺 XD

我們直接看一個例子。改一改上一題,如果我今天想透過用 tablet: 變化模式來實現當螢幕寬度大於 500 時讓按鈕從紅色轉為藍色呢? 其實也是用同樣的方法:

<button class="bg-red tablet:bg-blue">
  按鈕 1
</button>

<style>
.bg-red {
  background-color:red;
}

@media (min-width: 500px) {
  .tablet\:bg-blue {
    background-color:blue;
  }
}
</style>

而這麼一來,有了響應式的變化模式,我們就可以輕鬆簡單的幫任何樣式在不同螢幕寬度時產生變化,只需要加上一個 {RWD}: 作為前綴詞。
 

深色模式

好,這個呢就比較複雜了,近年來許多網站開始陸陸續續的支援深色模式,也就是能偵測裝置的系統的設定值來自動切換網站的配色,又或者是像有些網站還能夠手動切換,而這兩個觸發方式,Tailwind 都有支援,甚至還可以做到進入網站時依照系統設定,但還能隨時手動切換。

我們這邊先以 Tailwind 依照系統設定值切換的模式來舉例:

<button class="bg-red dark:bg-blue">
  按鈕 1
</button>

<style>
.bg-red {
  background-color:red;
}

@media (prefers-color-scheme: dark) {
  .dark\:bg-blue {
    background-color:blue;
  }
}
</style>

但不管是哪一種模式,都只需要加上 dark: 作為前綴詞即可。而依據模式選擇的不同,Tailwind 在編譯時會自動替換掉 dark: 的效果。 關於更多深色模式的設定,也是會在本系列的最後一天提到。
 

carrotPoint 客製化

看完了前面這些核心概念,很有可能產生下面這兩種想法:

  • 那個「inline class」真的太臭太長了,好痛苦
  • 萬一,提供的樣式都沒有符合我需求呢?

而解決這兩大問題,正是客製化所要說的重點:功能不足就用新增樣式吧!覺得太臭太長,就提取成元件吧!

提取成元件

其實一路看來就會發現 tailwind 真的可以幫你節省掉很多一直重複寫 css 樣式的時間,但取而代之的是那一長串的功能性 class 名稱。不過 tailwind 有提供一個特別的指令,可以幫你把一長串的功能性 class 縮減用一個 class 當代稱,而那個指令就是 @apply

一樣,又有請我們的按鈕 1,先來點一般的樣子:

<button class="bg-red dark:bg-blue p-3 text-white font-bold">
  按鈕 1
</button>

透過 @apply 指令,我們可以把原本臭長的 class 名稱全部移動到一個自定義的 class 名稱內。

<button class="btn1">
  按鈕 1
</button>

<style>
.btn1 {
  @apply bg-red dark:bg-blue p-3 text-white font-bold;
}
</style>
<!-- 注意,這個案例的內容不能直接套用在 tailwind 上,
這是簡化後適合學習用的範例 -->

但,很可能你又有想法了XDD。
「啊這不就變回去用那個很抽象的 class 了嗎!不就又沒辦法 UI 跟視覺一致了嗎!!!」,

關於這個嘛 ...我只能說 ... 因為有人就是有這個需求嘛!其實提取成元件的好處主要在於不用同時使用多個相同設計的元素維護一長串的 class

但不只有這個製作元件的方法,無論是我或是官方都更推薦你使用的方法是配合前端框架,然後製作成前端框架的元件,這樣既不用用到 @apply 指令,還能保持與 UI 設計視覺一致,因為每個相同設計的按鈕都被作為元件重複使用。
 

函數與指令

Tailwind 有提供一些方便的函數與指令可以使用,像是剛剛在提取成元件核心概念中所介紹到的 @apply 指令。而其實還有一些也很實用的指令,比如說:@layer

@layer 指令的用途蠻特別的。因為其實 Tailwind 的樣式有分為 basecomponentsutilities 三個層級,依照層級的高低來決定渲染 (樣式編譯時) 的順序。

Tailwind 層級的高低 (樣式權重):

base components utilities

我們再次有請按鈕 1 來為我們示範一下。

<button class="btn1">
  按鈕 1
</button>
<button class="btn1 bg-blue">
  按鈕 1 (藍色)
</button>

<style>
.btn1 {
  @apply bg-red dark:bg-blue p-3 text-white font-bold;
}
</style>
<!-- 注意,這個案例的內容不能直接套用在 tailwind 上,
這是簡化後適合學習用的範例 -->

這個範例,我們可以看到第二個按鈕 1 套用了整理後的樣式 .btn1,後面加上了 bg-blue 來讓我們的元件能基於 .btn1 的樣式延伸,只把背景色改成藍色。

但其實這個範例,是失敗的

如果你真的使用了 Tailwind 這麼做,會發現按鈕仍然是紅色。在一般 inline style 以及 class 中,較後面的樣式會覆蓋掉前面同屬性的樣式,舉例:

<button style="background-color:red;background-color:blue;">
  按鈕 1
</button>

<button class="btnRed btnBlue">
  按鈕 1
</button>

<style>
.btnRed {
  background-color:red;
}
.btnBlue {
  background-color:blue;
}
</style>

這兩個按鈕的背景顏色都會是藍色,因為紅色樣式覆蓋掉了可是兔兔,為什麼在 Tailwind 會失敗呀? 我們再看一次前面的例子這樣你比較方便對照:

<button class="btn1">
  按鈕 1
</button>
<button class="btn1 bg-blue">
  按鈕 1 (藍色)
</button>

<style>
.btn1 {
  @apply bg-red dark:bg-blue p-3 text-white font-bold;
}
</style>
<!-- 注意,這個案例的內容不能直接套用在 tailwind 上,
這是簡化後適合學習用的範例 -->

因為在 Tailwind 之中,若你在 class 裡使用到 @apply 指令,這個 class 的渲染順序會被落在 utilities 之後,而所有的功能性 class (Utility class) 顧名思義,就是屬於 utilities 層級,因此我們就得用上 @layer 指令,來幫我們的 .btn1 樣式定義層級,把層級降低來確保渲染順序。

<button class="btn1">
  按鈕 1
</button>
<button class="btn1 bg-blue">
  按鈕 1 (藍色)
</button>

<style>
@layer components {
  .btn1 {
    @apply bg-red dark:bg-blue p-3 text-white font-bold;
  }
}
</style>
<!-- 注意,這個案例的內容不能直接套用在 tailwind 上,
這是簡化後適合學習用的範例 -->

這樣就把 @layer 給介紹完了。關於這些更細部的內容可能不會在這次鐵人賽中講到,但如果要講,可能也會是第30天之後了~
 

增加基底樣式和新功能

Tailwind 其實不只是提供這些預設樣式,你可以依據自己的喜好來產生樣式規範或者新增你所需要的新樣式。Tailwind 在編譯時會讀取你的設定檔依照設定檔的內容產生相對應的樣式及功能性 class 的名稱供你直接使用,像前一節所提到的「Tailwind 的深色模式有兩種觸發方式可以選擇」,而這觸發方式的選擇就是要在設定檔中修改,這樣在編譯時就自動套用你的設定值。

而關於「增加基底樣式及新功能」的內容因為在碰到安裝之前這些都屬於太進階的東西,所以這邊不會講到,還請見諒。
 

carrotPoint 「蘿蔔槍打靶比賽成績計算」

其實看完上面這些核心概念的特性呀,剛好可以解釋到上一篇所說的:

「BS 雖然有很多元件可以直接幫元素加上 class 名稱就可以使用,但 Tailwind 沒有。」

正因為捨棄了傳統式的抽象化命名,所以才不會有如 Bootstrap 那種套用 class 名稱就可以變成元件的方便功能,因為現在你必須使用功能性 class 來組合出你想要的東西。

雖然我們不能說 BS 把傳統式寫法與 utility class 融合使用的這個巧思是一種錯誤,它初衷是好的,但仍然還是造成了一些不可避免的問題。

(P.S. 比如說它設計讓你用元件 class 和功能性 class 兩種來設計,但其實你卻用到了四種,像是 inline style 和 !!!immmportant)

 

感謝今天聽了這麼多廢話,但希望能夠對你有幫助。
然後...

從 Day 3 開始終於有實作啦!!

接著就要一步一步踏入兔子的國度與 Tailwind 的奇妙世界了,一起循序漸進慢慢學習,其實兔兔我自己也很期待呢!
 

carrotPoint 給你們的回家作業:

  • 請把這篇背起來明天抽考!
  • 睡飽飽,明天記得看文章!

關於兔兔們:


 


( # 兔兔小聲說 )

「你們猜猜我昨天睡哪窟 ...? 我明天再告訴你們!」


上一篇
Day 01:「排版神器 Tailwind CSS?」 - 和兔兔一起快速上手漂亮的元件開發
下一篇
Day 03:「開始乘風飛行!」- 樣式都不見了嗎?快用 Tailwind 輕鬆調整文字版式
系列文
排版神器 Tailwind CSS~和兔兔一起快速上手漂亮的元件開發!32

尚未有邦友留言

立即登入留言