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 给你们的回家作业:

  • 请把这篇背起来明天抽考!
  • 睡饱饱,明天记得看文章!

关於兔兔们:


 


( # 兔兔小声说 )

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


<<:  Day3: Big O — 时间复杂度与空间复杂度

>>:  Day 01 | 前言与赛程

Day 31 (Jq+JqUI)

1.意义 [] 很多个东西要拿出来 {} 多个变数形容她 function 重复做的事情 2.推敲过...

[Day27] 在 Codecademy 学 React ~ 用 useEffect 为游戏加上计时功能吧!

前言 想了想还是决定把 useEffect 走完XD 不然有讲 useState 没讲 useEff...

Day3

做好学习程序语言这件事,可以说已经成为了全民运动。在人类的历史中,我们总是尽了一切努力想搞懂学习程序...

03 - Alacritty - 终端机

macOS 的预设终端机为 Terminal.app ,对於使用终端频率较少的一般使用者是十分足够的...

【Day2】VsCode相关套件安装及建立React专案和React的小小介绍!!

上一篇有提到说,这次使用的IDE是超级好用的VsCode,相信各位看官们也都下载好了 安装完毕,打开...