# 10個CSS小技巧
:::info
:bulb: 一項投票調查中,關於Web開發人員合作中使他們最痛苦的技術,CSS位居榜首。
:::
CSS確實臃腫且難以全面學習,但這只是因為它在過去25年中不斷發展。它出現在1996年,netscape那時還是top瀏覽器。而如今RWD(Responsive Web Design)的想法也已有十多年,越來越多瀏覽器進入市場,它們又以不同方式去呈現CSS。這導致code在一個瀏覽器中工作,另一個又不工作,使得開發人員就必須在代碼中編寫一堆令人困惑的供應商前綴。
```css
.lame-sauce {
-webkit-transition: all 4s ease; /* chrome */
-moz-transition: all 4s ease; /* firefox */
-ms-transition: all 4s ease; /* IE */
-o-transition: all 4s ease; /* opera */
transition: all 4s ease;
}
```
所以我今天會介紹幾個最近在做side project查資料時,看到國外大佬使用的一些tips。讓我學習用現代功能編寫乾淨的CSS,同時避免3202年還在編寫意義不明的:poop:。
## :book: 學習Box Model
:::success
"學習時不要用==bootstrap==或==tailwind==這樣的框架。它們確實非常sexy,可以快速幫助你獲得一個漂亮的UI。但在你的project中用這類東西,就像結婚一樣,你不會學習到CSS基礎知識,當你主意變了,你還必須歷經離婚一般的痛苦。" ---Fireship
:::
當你學習基本的CSS,你將更好更自由的去控制你的code。
開始說回第一個標題。關於Box Model,你可以先把每個html元素都視為一個box,每個box都有自己的寬高,你可在內壁填充去擠壓內容。外部可以添加邊框,而周邊還有個不可見的範圍叫做邊距。
CSS中與佈局和位置相關的所有內容都受到Box Model的影響,所以關於如何應用就顯得更為重要。
打開瀏覽器的開發人員選項有圖化形界面可以參考。

## :book: 調試時使用Firefox
Firefox的開發工具更優秀,尤其是涉及CSS時。檢查一個元素時,能像在chrome中那樣分解Box Model,列出所有屬性細目,甚至也可以直接在上面編輯屬性。

它還在HTML中提供了有料的註釋,例如當一個元素使另一元素溢出時會有overflow標記。

它還為flex和grid提供了很好的圖形。

## :book: Flexbox好棒棒
關於佈局和元素間的定位,歷史上一直是CSS最具挑戰性的方面之一。就像問你如何在水平和垂直方向居中一個div這種古老問題。一種方法是給child絕對定位,然後用top和left屬性把它移動到右下角,然後反向位移50%讓它回到中間。但它非常不直觀,也讓看的人也覺得很有病。
```css
.classic {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
```
使用flex就相當於給div一個x、y軸,想要置中就讓它沿著水平軸和鉛直軸對齊就好了。
```css
.flex {
display: flex;
justify-content: center;
align-items: center;
}
```
雖然flex用起來挺簡便的,但如果你今天遇到一個很大還很複雜的UI,它的一個主要缺點就出來了。許多相交的行與列,會使你HTML中有很多容器或包裝元素。這些元素沒有語意意義,它們只會一直待在那裡。
```htmlmixed
<div class="row-wrapper">
<div class="column-wrapper">
<div class="box">💩</div>
<div class="box">💩</div>
</div>
<div class="column-wrapper">
<div class="box">💩</div>
<div class="box">💩</div>
</div>
<div class="column-wrapper">
<div class="box">💩</div>
<div class="box">💩</div>
</div>
</div>
```
```css
.row-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
}
.column-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
```
## :book: Grid好棒棒
上述的問題,grid佈局可以幫你解決。它不像felxbox只能處理單獨的行與列。而它看起來像舊時代的表格佈局。

你若使用grid,你的code會相對友善一些。你可以將children定義為一堆列元素,行列寬度可以使用模板列屬性定義。這裡注意一下fr值,它會與grid中其它列元素共享可用空間。我們還可以定義一些行,grid內元素都將自動定位。和flexbox或table layout相比,能減少許多HTML和CSS的code數量。
```htmlmixed
<div class="grid">
<i>😎</i>
<i>😎</i>
<i>😎</i>
<i>😎</i>
<i>😎</i>
<i>😎</i>
</div>
```
```css
.grid {
display: grid;
grid-template-columns: 1fr 500px 1fr;
grid-template-rows: 100px 200px;
place-items: center;
}
```

## :book: Clamp夾爛它
前面講了一堆關於佈局和定位的東西,而當我們在實現RWD時,我們要因應用戶的設備螢幕可用空間調整畫面呈現。這時候有很多方式能做到這點。
例如你可能有一篇文章,它首選寬度為50%。但在小螢幕上,你可能會希望它寬度固定在200px。而換到大螢幕時,又希望它固定在800px。你可以透過==media query==來實現。
```css
article {
width: 50%;
}
@media only screen and (max-width: 600px) {
article {
width: 200px;
}
}
@media only screen and (max-width: 1200px) {
article {
width: 800px;
}
}
```
但唯一的缺點是,==media query==會讓你在project變大時想自殺。
但好消息是你可以透過使用==min==、==max==、==clamp==之類的函數去改寫它。
```css
article {
width: clamp(200px, 50%, 600px);
}
```
## :book: 用emoji挑戰你的同事與上司
當你的className長到你想死的時候,試試看用emoji代替。為你煩躁的一天增添些許樂趣。


## :book: 使用Aspect Ratio
如果你曾經不得不編寫一個保持一定寬高比的RWD圖片或影片(好像有種既視感🤔,你說對吧,日程本),這東西會make u shock😮
例如今天你想放一個16x9的影片,你可能會被迫寫出一個像這樣的鳥東西。
```css
.container-16x9 {
position: relative;
padding-top: 56.25%;
}
video {
width: 100%;
position: absolute;
top: 0;
}
```
這時候,你可以使用==aspect-ratio==這屬性。
```css
video {
width: 100%;
aspect-ratio: 16/9;
}
```
但有個缺點是老地方不一定支援這屬性,這部份自己斟酌。
## :book: 創造變量
除了減少代碼量,更重要的一點是讓code更靈活,這樣在改寫和重構時才不會讓人想死。一種方式是用CSS自定義屬性和變量。像下列code中,我們在多個地方使用了相同顏色,當我們想更改顏色時,你會開始想鑽個洞把自己埋進去。
```css
p {
color: rgb(255, 0, 0);
}
h1 {
color: rgb(255, 0, 0);
}
h2 {
color: rgb(255, 0, 0);
}
```
而更好的方法是在==root==選擇器上定義一個全局變量,然後在需要的地方進行引用。
```css
:root {
--text-color: rgb(255, 0, 0);
}
p {
color: var(--text-color);
}
h1 {
color: var(--text-color);
}
h2 {
color: var(--text-color);
}
```
你甚至可以在tree更深的的地方重新定義去override。
```css
:root {
--text-color: rgb(255, 0, 0);
}
p {
--text-color: rgb(0, 255, 0);
color: var(--text-color);
}
```
抑或分解組合成得更細。
```css
:root {
--r: 255;
--g: 0;
--b: 0;
--text-color: rgb(var(--r), var(--g), var(--b));
}
p {
--text-color: rgb(0, 255, 0);
color: var(--text-color);
}
```
## :book: 花式calc
現在CSS不是傳統意義上的編程語言,但它確實能夠使用calc函數進行基本運算。了解這點的你,現在你可以快樂的和小學生一起做算術題了。
```css
calc(2px + 2px)
```
單位甚至不用相同。
```css
width: calc(100vw - 80px);
font-size: calc(1rem * 1.25);
padding: calc(5% + 2px);
```
更酷的是,你可以這樣做動畫,就能在不增加CSS代碼的情況下實現相同效果。
```htmlmixed
<style>
.drop {
animation: dropIn 1s ease forwards;
animation-delay: calc(var(--order) * 100ms);
}
@keyframes dropIn {
from { transform: translateY(-500px); }
to { transform: translateY(0); }
}
</style>
<i class="drop" style="--order: 1">1️⃣</i>
<i class="drop" style="--order: 2">2️⃣</i>
<i class="drop" style="--order: 3">3️⃣</i>
```
## :book: 狀態管理計數器
雖然剛才說CSS不是一種編程語言,但它實際上有一個內置的狀態管理機制,你可以追蹤你code中的運行記數,而不用多編寫一行JavaScript。
若你想對標題進行編號,最白癡的方法就長這樣。
```htmlmixed
<h1> 1. apple </h1>
<h1> 2. bird </h1>
<h1> 3. dog </h1>
```
當你突然想要從中間補上一個新的標題時,就會有種想去頂樓自由落體的感覺。
```htmlmixed
<h1> 1. apple </h1>
<h1> 2. bird </h1>
<h1> 3. cat </h1> ←←←←←←←←←←←←←←
<h1> 3. dog </h1>
```
聰明的方法是去root創建一個計數器,用==counter-reset==給一個你喜歡的名字,然後在需要應用的地方去用==counter-increment==遞增它,它將從0開始將1添加到dom的每個h1元素。
```css
:root {
counter-reset: headings;
}
h1 {
counter-increment: headings;
}
h1::before {
content: counter(headings);
}
```
## :book: focus-within的魔力
構建複雜下拉式選單時,你可能會認為這必須涉及一些JavaScript來管理選單的開啟和關閉state,但你可能也會想你若只透過純CSS可以做到多遠。
大家都很熟悉==focus==這偽類,當你進行表單輸入或單擊按鈕時,該偽類會應用於元素上。問題是,在構建下拉式選單時,你可能會使用==focus==打開選單,但當你點擊選單內的某個內容時,它會失去焦點並關閉。這就為何你會使用JavaScript去管理state。
但有趣的是,有個偽類叫做==focus-within==,如果任何children也有這偽類,它就會保持active的狀態。
這樣簡單的一個功能,可以多減少幾行用於管理打開和關閉state的JavaScript代碼。
```css
.dropdown {
opacity: 0;
visibility: hidden;
}
button:focus-withing .dropdown {
opacity: 1;
visibility: visible;
}
```
## :book: 結語
這樣,我們就簡單提完了幾個CSS技巧。還記的剛開頭提到的瀏覽器供應商前綴嗎,那東西就像皰疹一樣不會消失。但幸運的是我們有些有料的小工具可以讓它變得不成問題。
其中一個工具就是==PostCSS==(看起來像光明會來的),它使用一個叫做auto prefixer的工具,來自動添加所有供應商前綴。此外,它還允許你使用現代CSS功能,會幫你自動轉換代碼,以盡可能讓所有目標瀏覽器都支援。你還可以去看看==Sass==、==stylus==、==less==等預處理器。之後有緣再分享。
