Day 55:CSS 選擇器語法、層疊順序、Normal Flow、Position、Margin Collapsing、HTML/CSS vs JavaScript 除錯差異

今日學習主題

  • CSS 選擇器語法與層疊優先級

  • Normal Flow 排版機制

  • Block vs Inline 元素特性

  • Position 定位與 z-index 堆疊

  • Margin Collapsing 規則

  • HTML/CSS vs JavaScript 除錯差異


CSS 選擇器與層疊順序

從常見陷阱開始

問題情境:為什麼 <a class="a b">HELLO</a> 配上以下 CSS 會顯示紅色而非藍色?

a { color: red; }
b { color: blue; }

答案揭曉:因為 b 是元素選擇器,選中的是 <b> 標籤(如 <b>粗體</b>),而非 class="b"! developer.mozilla


核心概念 1:元素選擇器 vs 類別選擇器

元素選擇器(Element Selector)

直接使用 HTML 標籤名稱,不需要任何前綴符號: udn.realityripple

/* 選取所有 <a> 標籤 */
a {
  color: red;
}

/* 選取所有 <p> 標籤 */
p {
  font-size: 16px;
}

類別選擇器(Class Selector)

在 class 名稱前必須加上點(.)符號: developer.mozilla

/* 選取所有 class="menu" 的元素 */
.menu {
  background: blue;
}

/* 選取所有 class="btn-primary" 的元素 */
.btn-primary {
  padding: 10px;
}

對比表格

選擇器類型

語法

範例

選取目標

元素選擇器

elementname

a, div, p

HTML 標籤本身

類別選擇器

.classname

.menu, .btn

含有該 class 的元素


核心概念 2:HTML class 屬性的多類別語法

空格是分隔符號

在 HTML 的 class 屬性中,空格用來分隔多個類別名稱: udn.realityripple

<!-- 這個 div 同時擁有兩個類別:primary 和 button -->
<div class="primary button"></div>

<!-- 這個 a 同時擁有三個類別:nav, link, active -->
<a class="nav link active">Home</a>

類別名稱本身不能有空格

單一類別名稱內部不能包含空格,需要多個單字時使用連字符號:

<!-- ✓ 正確:使用 kebab-case -->
<button class="btn-primary"></button>

<!-- ✗ 錯誤:會被解讀成兩個類別 btn 和 primary -->
<button class="btn primary"></button>

選擇多個類別的 CSS 語法

/* 選中所有 class="primary" 的元素 */
.primary { color: red; }

/* 選中所有 class="button" 的元素 */
.button { padding: 10px; }

/* 選中「同時擁有 primary 和 button」的元素(中間無空格) */
.primary.button { border: 1px solid; }

/* 選中「primary 裡面的 button」(中間有空格) */
.primary .button { margin: 5px; }

核心概念 3:CSS 層疊順序(Cascade Order)

層疊的運作原理

CSS 像油漆工在牆上一層層塗油漆,後塗的會覆蓋先塗的。 developer.mozilla

優先權判斷三步驟

當多條 CSS 規則衝突時,瀏覽器按照這個順序判斷: developer.mozilla

  1. 優先級(Specificity):選擇器越精準,優先權越高

  2. 來源順序(Source Order):優先級相同時,後面的覆蓋前面的

  3. 重要性(!important):強制覆蓋所有規則(不建議濫用)

優先級計算表

選擇器類型

優先級值

範例

說明

Inline style

1-0-0-0

<div style="color:red">

最高

ID

0-1-0-0

#header

高

Class / 偽類 / 屬性

0-0-1-0

.menu, :hover, [type="text"]

中

Element / 偽元素

0-0-0-1

a, div, ::before

低

w3schools

實例解析 1:優先級相同

<a class="a b">HELLO</a>
.a { color: red; }   /* 優先級:0-0-1-0 */
.b { color: blue; }  /* 優先級:0-0-1-0 */

結果:藍色
原因:兩個選擇器優先級相同,.b 在後面所以覆蓋 .a developer.mozilla

實例解析 2:優先級不同

<a class="a b">HELLO</a>
a.b { color: blue; }  /* 優先級:0-0-1-1 (1個元素+1個類別) */
.a { color: red; }    /* 優先級:0-0-1-0 (1個類別) */

結果:藍色
原因:a.b 優先級較高,不管 CSS 出現順序 developer.mozilla


原始題目完整解析

題目:

<a class="a b">HELLO</a>
a{ color:red }
b{ color:blue }

拆解過程:

  1. HTML 解讀:這是一個 <a> 標籤,同時擁有兩個類別:a 和 b

  2. CSS 解讀:

    • a{ color:red } 是元素選擇器,選中所有 <a> 標籤 ✓

    • b{ color:blue } 是元素選擇器,選中所有 <b> 標籤(如 <b>粗體</b>) ✗

  3. 結果:只有第一條規則套用,顯示紅色

正確寫法:

a{ color:red }     /* 選中 <a> 標籤 */
.b{ color:blue }   /* 選中 class="b" 的元素 */

這時兩條規則都會套用,.b 優先級較高,最終顯示藍色。


易錯提醒

1. 類別選擇器忘記加點

/* ✗ 錯誤:這是選 <menu> 標籤,不是 class="menu" */
menu { color: red; }

/* ✓ 正確:這才是選 class="menu" */
.menu { color: red; }

2. 混淆元素與類別

b { color: blue; }   /* 選 <b> 標籤 */
.b { color: blue; }  /* 選 class="b" */

3. HTML class 順序不影響結果

<!-- 這兩個完全一樣 -->
<div class="a b"></div>
<div class="b a"></div>

CSS 規則的出現順序才會影響層疊結果,HTML 中 class 的順序無關。

4. 多類別選擇器的空格差異

.a.b { }   /* 同時擁有 a 和 b 類別的元素 */
.a .b { }  /* a 類別元素裡面的 b 類別元素 */

Normal Flow:元素排列的預設規則

**Normal Flow(正常流)**是瀏覽器排版的預設模式,所有「佔空間」的元素都在這個流程裡排隊。在 normal flow 中,元素只有兩種排列方式:block(區塊級) 或 inline(行內級)。 docs3.w3cub


Block 元素:垂直堆疊的排版框

排列規則

Block 元素會「獨佔一整行」,一個接一個垂直往下堆疊。 udn.realityripple

<div>第一個區塊</div>
<p>第二個區塊</p>
<div>第三個區塊</div>

視覺結果:

每個 block 元素自動換行,就算內容很短也會佔滿父元素的寬度。 udn.realityripple

Block 元素的特性

特性

說明

寬度

預設佔滿父元素 100% 寬度

高度

由內容撐起,可設定 height

可設定尺寸

width、height 都有效

可設定留白

margin、padding 四個方向都有效

換行

前後都會自動換行

常見的 Block 元素

  • <div> - 通用容器

  • <p> - 段落

  • <h1> ~ <h6> - 標題

  • <ul> <ol> <li> - 列表

  • <section> <article> <header> <footer> - 語意化容器


Inline 元素:水平並排的文字流

排列規則

Inline 元素會「在同一行內橫向排列」,像文字一樣左右相鄰,遇到容器邊界才換行。 udn.realityripple

<span>第一段</span>
<strong>第二段</strong>
<a href="#">連結</a>

視覺結果:

所有 inline 元素會擠在同一行,除非容器寬度不夠才會自動換行。 udn.realityripple

Inline 元素的特性

特性

說明

寬度

只佔內容所需的寬度

高度

由內容撐起,無法設定 height

可設定尺寸

width、height 都無效

可設定留白

margin、padding 只有左右有效,上下無效

換行

不會自動換行,和其他 inline 元素排在同一行

常見的 Inline 元素

  • <span> - 通用行內容器

  • <a> - 超連結

  • <strong> <em> - 文字強調

  • <img> - 圖片(特殊的 inline 元素)

  • <input> <button> - 表單元素


Normal Flow 的核心規則

1. Block 元素參與 Block Formatting Context

  • 垂直方向一個接一個堆疊

  • 上下 margin 會發生「margin collapsing(合併)」

  • 寬度預設是父元素的 100%

udn.realityripple

2. Inline 元素參與 Inline Formatting Context

  • 水平方向左右並排

  • 遵循文字排版規則(對齊基線、響應 text-align)

  • 寬度由內容決定,無法手動設定

udn.realityripple

3. Block 可以包含 Inline,但 Inline 不能包含 Block

<!-- ✅ 正確:Block 包 Inline -->
<div>
  這是段落,裡面有 <span>行內元素</span>。
</div>

<!-- ❌ 錯誤:Inline 包 Block -->
<span>
  <div>區塊元素</div> <!-- 語意錯誤,瀏覽器可能修正 -->
</span>

Display 屬性:改變元素在 Normal Flow 的行為

雖然 HTML 元素有預設的 block/inline 屬性,但可以用 CSS 的 display 改變: udn.realityripple

/* 把 inline 元素改成 block */
span {
  display: block; /* 現在 span 會獨佔一行 */
  width: 200px;   /* 可以設定寬度了 */
}

/* 把 block 元素改成 inline */
div {
  display: inline; /* 現在 div 會並排 */
  width: 200px;    /* 無效,因為 inline 不能設定寬度 */
}

常用的 Display 值

值

行為

適用情境

block

獨佔一行,可設定寬高

容器、區塊佈局

inline

並排同行,不可設定寬高

文字樣式、小標籤

inline-block

並排同行,但可設定寬高

按鈕、卡片並排

none

完全隱藏,不佔空間

條件顯示


Inline-Block:兼具兩者優點

display: inline-block 是特殊的混合模式: udn.realityripple

.button {
  display: inline-block;
  width: 100px;      /* 可以設定寬高(像 block) */
  height: 40px;
  padding: 10px;     /* 上下 padding 有效 */
  margin: 10px;      /* 上下 margin 有效 */
  /* 但會和其他元素並排(像 inline) */
}

特性:

  • 對外:像 inline 一樣橫向排列,不會換行

  • 對內:像 block 一樣可以設定寬高和四方向留白

常見用途:導覽列按鈕、圖標並排、響應式卡片佈局。


Position 與 z-index:脫離 Normal Flow

佔空間 vs 不佔空間

CSS 的「佔空間 / 不佔空間」,其實是在講元素有沒有參與正常排版流程(normal flow)。 udn.realityripple

什麼是「佔空間」(在 normal flow 裡)

在 normal flow 裡,元素會跟其他元素「排隊、互相推擠」。 udn.realityripple

  • 一個 block 元素(例如 <div>) 會一個接一個往下堆疊

  • 調整它的 margin、padding、height 時,其他元素會跟著被推開或被擠下去

  • 這種情況,我們就說:這個元素有佔版面空間,是 in flow 的元素

什麼是「不佔空間」(被拿出 normal flow)

當元素被設成 position: absolute 或 position: fixed 時,會被移出 normal flow。 udn.realityripple

  • 瀏覽器排版其他元素時,完全當它不存在

  • 它原本在 normal flow 要站的位置,會被後面的元素填補上來

  • 它的位置改由 top / right / bottom / left(配合最近的定位父元素或視窗)決定

z-index:便利貼的「圖層順序」

當多張便利貼重疊時,z-index 決定誰在上面、誰在下面。

.note-1 {
  position: absolute;
  z-index: 1; /* 比較低,會在下層 */
}

.note-2 {
  position: absolute;
  z-index: 10; /* 數字越大越上層 */
}

數字越大,越在上面。可以用負數讓元素沉到更下層。

重要限制:只對「定位元素」有效

z-index 只對有設定 position 的元素才會作用(除了 position: static)。

/* ❌ 無效:沒有設定 position */
.box {
  z-index: 999; /* 不會有任何效果 */
}

/* ✅ 有效:搭配定位屬性 */
.box {
  position: relative; /* 或 absolute / fixed / sticky */
  z-index: 999;      /* 現在才有效 */
}

堆疊上下文(Stacking Context):便利貼的「群組規則」

當父元素設定了 position + z-index 時,會建立一個堆疊上下文(stacking context)。

關鍵規則:子元素的 z-index 只在父元素的群組內比較,無法跨群組競爭。


Margin Collapsing:垂直外距合併

什麼時候會發生 margin Collapsing

只會發生在垂直的上下 margin,而且元素要在 normal flow 的 block 排版裡。 developer.mozilla

1. 同一層 block 兄弟元素相鄰

<p class="a">段落 A</p>
<p class="b">段落 B</p>
.a {
  margin-bottom: 20px;
}

.b {
  margin-top: 40px;
}

你可能以為 A 和 B 之間距離 = 20 + 40 = 60px,
實際上只會是 40px(較大的那個),這就叫「margin 重疊(collapsing)」。 developer.mozilla

規則:同一層、彼此相鄰、都是 block、沒有東西隔開(沒有邊框、padding、別的元素),上下 margin 就會合成一個,取最大值。 geeksforgeeks

2. 父元素和第一個/最後一個子元素之間

<div class="parent">
  <p class="child">文字</p>
</div>
.parent {
  margin-top: 50px;
  /* 沒有 padding-top、border-top、高度/內容 */
}

.child {
  margin-top: 30px;
}
  • 這時你畫面最上方看到的外距不是 50 + 30

  • 而是 max(50, 30) = 50px,等於子元素的 margin 被「傳到」父元素外面去了

developer.mozilla

3. 空的 block 元素上下 margin 自己重疊

.box {
  margin-top: 30px;
  margin-bottom: 50px;
  /* 沒有內容、沒有 padding、沒有 border、高度自動 */
}

這個 .box 的上、下 margin 會互相重疊,變成一個 50px 的外距(取較大)。 developer.mozilla

4. 什麼情況「不會」重疊


跟「實體排版計算」的關聯:為什麼要設計成會重疊?

可以用「書頁排版」來比喻 margin 的設計思路:

1. 把「段落間距」當成一個概念(而不是兩個分開加總)

在排版一頁文章時,兩個段落之間通常會設計一個「段落間距」,而不是「上一段底下 10pt + 下一段上面 10pt = 合起來 20pt」,否則越多段落,間距越誇張。 joshwcomeau

CSS 設計者選擇:「如果上下兩個 margin 碰在一起,就把它視為一個段落間距」→ 所以只取最大那個,不做相加。 joshwcomeau

2. 避免巢狀結構讓間距意外變大

實體排版如果一個段落包在框裡(例如卡片),你通常希望「外框 + 內文」整體看起來的段落間距還是符合版面設計,而不是框裡的 margin 又加一層父容器 margin。 developer.mozilla


實務開發的心智模型整理

  1. margin 重疊只發生在「垂直、block、normal flow」這個世界裡。 geeksforgeeks

  2. 如果你看到「為什麼我設很多 margin,間距卻沒想像中大」,90% 是重疊在一起了。

  3. 要「打斷」重疊,可以在中間加任何一層「墊片」:

    • padding

    • border

    • 實際內容(哪怕是一個看不到的 ::before)

    • 或把那個容器變成 flex/grid

joshwcomeau


HTML/CSS vs JavaScript:語言特性與除錯差異

核心觀念

HTML/CSS 和 JavaScript 屬於完全不同類型的語言,這決定了它們的錯誤處理機制和除錯方式。

特性

HTML/CSS

JavaScript

語言類型

宣告式(Declarative)

命令式(Imperative)

錯誤處理

忽略無效規則,繼續執行

拋出錯誤,中斷執行

IDE 支援

靠規則比對提示

直接從引擎獲得錯誤

除錯方式

視覺檢查 + DevTools

Console 錯誤訊息 + Debugger


HTML/CSS:宣告式語言

運作邏輯

你只是「描述想要的結果」,瀏覽器會盡力實現,遇到錯誤不會中斷執行。

比喻:像「許願清單」,店員盡量滿足,但缺貨時會跳過繼續處理下一項,不會因為一個錯誤就罷工。

CSS 錯誤處理規範

/* 瀏覽器遇到無效屬性會直接忽略,繼續套用其他規則 */
.box {
  colur: red;      /* 拼字錯誤 → 瀏覽器忽略 */
  color: blue;     /* 正確語法 → 套用 ✓ */
  widht: 100px;    /* 拼字錯誤 → 瀏覽器忽略 */
  height: 100px;   /* 正確語法 → 套用 ✓ */
}

CSS 規範明確規定:瀏覽器必須忽略無效的屬性/值,不會拋出任何錯誤訊息。這是設計選擇,確保即使開發者寫錯,使用者仍能看到網頁內容(容錯性 fault tolerance)。


JavaScript:命令式語言

運作邏輯

你下達「執行步驟」,引擎必須逐行執行,遇到錯誤會立即拋出例外並中斷。

比喻:像「煮菜食譜」,每一步都要正確執行,缺了鹽就會報錯停止,不會繼續煮下去。

JS 錯誤處理機制

// 1. 語法錯誤(SyntaxError) - 執行前就會偵測
const user = nul;  // ❌ SyntaxError: Unexpected token
console.log(user); // 這行不會執行

// 2. 執行時錯誤(Runtime Error)
const user = null;
console.log(user.name);  // ❌ TypeError: Cannot read property 'name' of null

// 3. 錯誤捕捉機制
try {
  riskyOperation();
} catch (error) {
  console.error('捕捉到錯誤:', error.message);
  // 程式可以繼續執行
}

DevTools 檢查元素:五大核心功能

1. 追蹤樣式覆蓋(Override)

用途:找出「為什麼我寫的樣式沒生效」

/* 在 Styles 面板會看到: */
.button {
  color: blue;     /* 被刪除線蓋掉 */
}

#submit-btn {
  color: red;      /* ✓ 生效,因為 ID 權重更高 */
}

操作:被刪除線劃掉的屬性就是被覆蓋的規則。

2. 查看權重/特異性(Specificity)

用途:解釋「為什麼這條規則贏了」

權重計算規則: developer.mozilla

(a, b, c)
a = ID 選擇器數量
b = class/屬性/偽類選擇器數量
c = 標籤選擇器數量

操作:滑鼠懸停在選擇器上,DevTools 會顯示權重計算。

3. 查看計算樣式(Computed)

用途:看到最終生效的值(經過繼承、覆蓋、預設值計算後)

使用時機:當 Styles 面板規則太多看不出問題時,直接看 Computed 找到最終值,再點擊展開追蹤來源。

4. 即時修改測試

用途:快速試錯,不用重新載入

實務流程:

  1. 右鍵點擊元素 → 檢查

  2. 在 Styles 面板點擊屬性值

  3. 修改數值,瀏覽器立即套用

  4. 確認效果後,回去改原始 CSS 檔案

注意:DevTools 的修改是暫時的,重新整理會消失!

5. 視覺化佈局工具

  • Box Model 檢視器:查看元素實際的 margin、border、padding、content

  • Flexbox/Grid 檢視器:顯示 flex items 對齊狀況、grid cells 佈局

  • 偽類狀態切換:強制觸發 :hover、:focus、:active 等狀態


重點整理

Normal Flow 的三個層級

  1. Normal Flow(正常流):元素預設的排版系統 udn.realityripple

  2. Block Formatting Context:block 元素垂直堆疊的規則 udn.realityripple

  3. Inline Formatting Context:inline 元素水平並排的規則 udn.realityripple

記憶口訣

  • Block = 獨佔一行的框:可設定寬高,垂直堆疊

  • Inline = 並排的文字流:不可設定寬高,水平排列

  • Inline-Block = 並排的框:兼具兩者優點

脫離 Normal Flow 的方法

只要設定以下屬性,元素就會離開 normal flow: udn.realityripple

  • position: absolute 或 fixed

  • float: left 或 right

  • display: flex 或 grid(子元素會進入新的排版上下文)


延伸學習資源


筆記時間:2026-02-06
核心學習:CSS 選擇器語法、層疊順序、Normal Flow、Position、Margin Collapsing、HTML/CSS vs JavaScript 除錯差異
學習階段:Phase 2 - JavaScript 基礎 + CSS 切版