Day 60:前端切版重構技術筆記

目錄

  • Part 1: CSS 命名系統

  • Part 2: 重構過程與問題解決


Part 1: CSS 命名系統

業界常見的 CSS 命名方式

1. BEM (Block Element Modifier)

核心概念:用「區塊-元素-修飾符」三層結構命名,讓 class 名稱本身就能表達元素的身份和關係。 frontendmentor

命名規則:

.block              → 獨立元件
.block__element     → 元件內的子元素
.block--modifier    → 元件的變化版本

實例:

<div class="card">
  <h2 class="card__title">標題</h2>
  <p class="card__text">內容</p>
  <button class="card__btn card__btn--primary">按鈕</button>
</div>

優勢:

  • 權重扁平化:所有選擇器權重統一為 0-1-0 zoocha

  • 結構清晰:一看 class 名稱就知道元素關係

  • 避免樣式衝突:命名空間隔離

適用情境:

  • 中大型專案

  • 需要明確元件邊界

  • 團隊協作開發


2. OOCSS (Object-Oriented CSS)

核心概念:把 CSS 當成「樂高積木」,強調「結構與外觀分離」和「容器與內容分離」。 developer.mozilla

實作方式:

/* 通用結構類別 */
.list { /* 列表基礎結構 */ }
.list-item { /* 列表項目結構 */ }

/* 特定外觀類別 */
.nav-list { /* 導航列表樣式 */ }
.nav-item { /* 導航項目樣式 */ }

HTML 使用:

<ul class="list nav-list">
  <li class="list-item nav-item">...</li>
</ul>

適用情境:需要大量重複使用相同視覺模式時(例如 Bootstrap)。 hackmd


3. SMACSS (Scalable and Modular Architecture for CSS)

核心概念:不是命名規則,而是「CSS 檔案組織架構」,把樣式分成 5 大類: frontendmentor

  1. Base (基礎):全域預設樣式 (body, a, h1)

  2. Layout (佈局):主要版面結構 (.header, .main)

  3. Module (模組):可重複使用的元件 (.nav, .card)

  4. State (狀態):視覺狀態切換 (.is-active, .is-hidden)

  5. Theme (主題):視覺風格變化 (顏色、字型)

實作方式:

/* base.css */
body { font-family: Arial; }

/* layout.css */
.nav { background-color: #f0f0f0; }

/* module.css */
.nav-link { padding: 10px; }

/* state.css */
.nav-link.is-active { font-weight: bold; }

適用情境:大型專案需要多個 CSS 檔案分工管理時。 developer.mozilla


4. SUIT CSS

核心概念:專為 React/Vue 等元件化框架 設計,用 PascalCase 命名元件。 frontendmentor

命名規則:

ComponentName              → .Nav (元件)
ComponentName-descendent   → .Nav-item (子元素, camelCase)
ComponentName--modifier    → .Nav--primary (變化版本)
ComponentName.is-state     → .Nav-link.is-active (狀態)

實作範例:

<nav class="Nav Nav--primary">
  <ul class="Nav-list">
    <li class="Nav-item">
      <a class="Nav-link is-active">About</a>
    </li>
  </ul>
</nav>

適用情境:使用 React/Vue 等元件框架,希望 CSS 命名與 JS 元件命名風格一致時。 frontendmentor


5. ITCSS (Inverted Triangle CSS)

核心概念:用「倒三角形」組織 CSS 層級,從最通用到最具體。 reddit

層級結構:

Settings    → 變數、配置
Tools       → Mixins、函式
Generic     → Reset、Normalize
Elements    → 原生 HTML 標籤
Objects     → 佈局模式 (OOCSS 物件)
Components  → 具體元件 (BEM 命名)
Utilities   → 工具類別 (!important 覆蓋)

適用情境:超大型專案需要嚴格控制 CSS 優先級 (specificity) 時。 developer.mozilla


6. Atomic CSS (原子化 CSS)

核心概念:每個 class 只做「一件事」,像寫內聯樣式但用 class。 mastheadtechnology

實作方式:

.mt-10 { margin-top: 10px; }
.flex { display: flex; }
.text-center { text-align: center; }

HTML 使用:

<div class="flex mt-10 text-center">...</div>

代表框架:Tailwind CSS、Tachyons。 samsontobiy.hashnode

適用情境:快速原型開發、設計系統統一、不想寫自訂 CSS 時。 mastheadtechnology


CSS 命名方式比較表

方法

核心目標

命名風格

學習曲線

最適合專案類型

BEM

元件獨立性

block__element--modifier

中等

中大型傳統網站

OOCSS

樣式重用性

結構+外觀分離

中等

需要大量重複模式

SMACSS

檔案組織

分類管理

中等

大型多人協作

SUIT CSS

元件化框架

ComponentName-element

中等

React/Vue 專案

ITCSS

層級控制

倒三角結構

高

超大型專案

Atomic CSS

快速開發

單一功能類別

高(需記憶大量類別)

快速迭代專案


BEM 與 Atomic CSS 混用

業界經常混用 BEM 和 Atomic CSS(特別是 Tailwind CSS),這種組合能取各自優勢。 sitepoint

模式 1: BEM 主體 + Utility 輔助(最常見)

核心思想:用 BEM 定義元件結構,用 Atomic 類別處理通用樣式。 reddit

<div class="card">
  <h2 class="card__title">Title</h2>
  <p class="card__text mt-4 text-center">Content</p>
  <button class="card__btn card__btn--primary px-6 py-2">Click me</button>
</div>

模式 2: Tailwind @apply + BEM 命名

核心思想:用 Tailwind 的 @apply 指令,把 utility classes 打包成 BEM 類別。 wearecogworks

.card {
  @apply bg-white rounded-lg shadow-md p-6;
}

.card__title {
  @apply text-2xl font-bold mb-4;
}

.card__btn--primary {
  @apply bg-blue-500 text-white hover:bg-blue-600;
}

優勢:

  • HTML 保持乾淨 wearecogworks

  • 保留 BEM 的結構化

  • 享受 Tailwind 的快速開發


模式 3: Utility-first + BEM 前綴

核心思想:主要用 Atomic CSS,只在複雜元件或需要 JavaScript 鉤子時用 BEM。 simplethread

<!-- 簡單元件: 純 utility classes -->
<div class="bg-white p-6 rounded-lg shadow">
  <h2 class="text-2xl font-bold mb-4">Title</h2>
</div>

<!-- 複雜元件: BEM + utilities -->
<div class="dropdown" data-component="dropdown">
  <button class="dropdown__trigger px-4 py-2 bg-blue-500 text-white">
    Open Menu
  </button>
  <ul class="dropdown__menu hidden absolute bg-white shadow-lg">
    <li class="dropdown__item px-4 py-2 hover:bg-gray-100">Item 1</li>
  </ul>
</div>

為什麼這樣做:

  • Utility classes 可能會變(從 mt-4 改成 mt-6)

  • BEM 類別是「語意化的 JavaScript 鉤子」,不會因樣式調整而改動 simplethread


何時用 BEM?何時用 Atomic CSS?

用 BEM 的時機:

  • 元件特有的樣式

  • 需要 JavaScript 操作

  • 複雜的元件變體

用 Atomic CSS 的時機:

  • 通用間距、顏色、字體

  • 快速原型、迭代開發

  • 響應式變化


CSS 權重計算規則

CSS 權重用 ID-CLASS-TYPE 三欄位表示,由左至右比較: developer.mozilla

欄位

包含的選擇器

權重值

ID

#header

1-0-0

CLASS

.logo、[type="text"]、:hover

0-1-0

TYPE

header、p、::before

0-0-1

無權重

*、+、>

0-0-0

比較規則:從左至右逐欄比較,誰的數字大誰贏。 hackmd

範例

#myId .myClass p { /* 1-1-1 */
  color: red;
}

.myClass .myClass .myClass p { /* 0-3-1 */
  color: blue;
}

雖然藍色有 3 個 CLASS,但紅色有 1 個 ID,所以紅色贏(ID 欄位 1 > 0)。 developer.mozilla

重構前後權重對比

元素

重構前

重構後

差異

Logo 圖片

header .logo img = 0-1-2

.header__logo-img = 0-1-0

降低 2 個 TYPE

Nav 項目

header nav li = 0-0-3

.header__nav-item = 0-1-0

提高但統一

Footer 圖示

footer .social-media li img = 0-1-3

.footer__social-media-icon = 0-1-0

降低 3 個 TYPE

核心發現:重構後所有選擇器權重都變成 0-1-0(單一 CLASS)。 cloudfour

BEM 權重扁平化的優勢

重構前:權重金字塔(容易失控)

#header nav li.active     (1-1-2) ← 要用很複雜的選擇器
footer .social li         (0-1-2)
nav li                    (0-0-2)
.button                   (0-1-0) ← 很難覆蓋上面的樣式

重構後:權重扁平化(完全可控)

.header__nav-item--active  (0-1-0)
.header__nav-item          (0-1-0)
.footer__social-icon       (0-1-0)
.hero__title               (0-1-0)

優勢:

  1. 覆蓋樣式只需在 HTML 加 class,不用改 CSS 權重

  2. 後寫的自動覆蓋前寫的,符合直覺

  3. 完全避免 !important zoocha


Part 2: 重構過程與問題解決

專案背景

作業目標:練習用 box model 推擠出正確的版面,將傳統 CSS 寫法重構為 BEM 風格。

技術限制:

  • 先少用 position 和 float

  • 練習 inline-block、text-align、vertical-align

  • 不使用 Flexbox/Grid(未來才學)


重構前的 HTML/CSS 結構

Header 部分(重構前)

HTML:

<header>
  <div class="logo">
    <img src="./public/logo.png" alt="網站 logo" />
    <p>ZERO TYPE</p>
  </div>
  <nav>
    <ul>
      <li><span>Home</span></li>
      <li>Feature</li>
      ...
    </ul>
  </nav>
</header>

CSS:

header {
  position: relative;
  border: 5px solid black;
  background-color: #f2f2f2;
}

header nav {
  position: absolute;
  top: 50%;
  left: 300px;
  transform: translateY(-50%);
}

header .logo img {
  width: 60px;
}

問題:

  1. 沒有使用 class 命名,權重不一致

  2. 依賴 position: absolute,寫死 left: 300px

  3. 權重分散(0-0-1 到 0-1-2 不等)


重構後的結構(第一版)

改用 BEM 命名

HTML:

<header class="header">
  <div class="header__logo-zone">
    <div class="header__logo">
      <img class="header__logo-img" src="./public/logo.png" alt="網站 logo" />
    </div>
    <p class="header__logo-text">ZERO TYPE</p>
  </div>
  <nav>
    <ul class="header__nav">
      <li class="header__nav-item">Home</li>
      <li class="header__nav-item">Feature</li>
      ...
    </ul>
  </nav>
</header>

CSS:

.header {
  border: 5px solid black;
  background-color: #f2f2f2;
}

.header__logo-img {
  width: 100%;
  height: auto;
}

.header__nav-item {
  color: gray;
  display: inline-block;
  margin-right: 100px;
}

改進:

  • 所有選擇器權重統一為 0-1-0

  • 移除 position: absolute 依賴

  • 命名語意化


遇到的問題與解決方案

症狀:改用扁平化 HTML 後,Footer 社群媒體區塊視覺上偏下。

原因:<ul> 預設有 padding-left: 40px 和 margin,導致 transform: translateY(-50%) 計算的「50%」包含了 padding,所以視覺上會偏移。 developer.mozilla

解法:重置 ul 預設樣式

.footer__social-media {
  margin: 0;
  padding: 0;
  list-style: none;
}

學習重點:

  • <ul> 和 <ol> 一定要重置 margin、padding、list-style developer.mozilla

  • absolute + transform 對盒模型很敏感,任何 padding/margin/border 都會影響計算


問題 2: text-align: justify 單行無效

症狀:想用 text-align: justify 讓元素兩端對齊(左右分散),但只有一行時無效。

原因:text-align: justify 只對多行文字有效,單行時不會觸發兩端對齊。 developer.mozilla

解法:用偽元素創造「第二行」

.hero {
  text-align: justify;
}

.hero::after {
  content: '';
  display: inline-block;
  width: 100%;  /* 撐滿整行,讓前一行「兩端對齊」*/
  height: 0;    /* 不佔高度 */
  overflow: hidden;
}

原理:

沒有偽元素(單行):
[Intro ················ ImgBox]
→ justify 無效

有偽元素(兩行):
[Intro ················ ImgBox]  ← 變成「第一行」,兩端對齊生效
[::after 撐滿第二行]  ← 偽元素佔位

副作用:偽元素會撐開容器高度,需要設定 height: 0 避免。 developer.mozilla


問題 3: column-count 位置錯誤

初始錯誤:用單一 <p> + <br> 分隔段落

HTML(錯誤):

<p class="content__text">
  段落1<br>
  <br>
  段落2<br>
  <br>
  段落3
</p>

問題:

  1. 語意錯誤:6 個段落應該是 6 個 <p>,不是 1 個 <p> 加 5 個 <br>

  2. 無障礙問題:螢幕閱讀器會把整段當成「一個段落」

  3. 無法單獨控制樣式:無法給「第一段加粗」、「第三段換顏色」

  4. SEO 不友善:搜尋引擎無法正確解析段落結構

正確寫法:

<article class="content">
  <h2 class="content__title">This is just a placeholder.</h2>
  <p class="content__text">段落1</p>
  <p class="content__text">段落2</p>
  <p class="content__text">段落3</p>
  <p class="content__text">段落4</p>
  <p class="content__text">段落5</p>
  <p class="content__text">段落6</p>
</article>

CSS:

.content {
  column-count: 2;      /* 父容器設定分欄 */
  column-gap: 20px;
}

.content__title {
  column-span: all;     /* 標題橫跨兩欄 */
}

.content__text {
  color: gray;
  line-height: 1.2;
  break-inside: avoid;  /* 避免段落被切斷 */
}

學習重點:

  • <br> 應該用於詩歌、地址等「同段落內的換行」,不該用於分隔不同段落

  • column-count 應該在父容器,讓整篇文章分欄,而非每個段落內部分欄


問題 4: Header 視窗縮小時會換行

症狀:視窗寬度稍微減少時,Nav 會被推到第二行。

原因分析:

視窗最小寬度 = 1200px(body min-width)
Header 可用寬度 = 1200px - 150px(左padding)- 50px(右padding)= 1000px

Logo 區佔用 = 60px + 120px(margin-right)= 180px
Nav 區需要 = 5個項目 × 150px(間距)+ 文字寬度(約350px)= 950px

總共需要 = 180px + 950px = 1130px
可用寬度 = 1000px

1130px > 1000px → 放不下!

解法 1:加大視窗

body {
  min-width: 1400px;  /* 從 1200px 改成 1400px */
}

/* 新的可用寬度 = 1400px - 200px = 1200px > 1130px ✅ */

解法 2:減少間距

.header__nav-item {
  margin-right: 100px;  /* 從 150px 改成 100px */
}

/* Nav 寬度 = 4個 × 100px + 350px = 750px */
/* 總需要 = 180px + 750px = 930px < 1000px ✅ */

學習重點:

  • inline-block 元素換行時,整個元素會掉到下一行(不是內容換行)

  • 寬度計算公式:可用寬度 = 視窗寬 - 左右 padding,需要寬度 = 元素1 + 間距 + 元素2

  • 目前階段需要「精確計算每個數值」,未來學 Flexbox 後瀏覽器會自動處理


問題 5: 圖片底部有空隙

症狀:inline 圖片容器下方有約 3-4px 的空隙。

原因:瀏覽器預設給 inline 元素留空間放英文小寫字母的下半部(如 g、y、p 的下半部)。 developer.mozilla

解法:

.header__logo-img,
.hero__img {
  vertical-align: bottom;  /* 對齊底線,消除空隙 */
}

學習重點:inline 或 inline-block 的圖片都要加 vertical-align: bottom 或改成 display: block。


Box Model 寬度計算原理

基本公式

可用寬度 = 視窗寬度 - 左右 padding
需要寬度 = 元素1寬度 + 間距 + 元素2寬度

如果 需要寬度 > 可用寬度,第二個元素會掉到下一行。

實際範例

Header 計算:

視窗寬度 = 1200px
Header padding = 150px(左)+ 50px(右)= 200px
Header 可用寬度 = 1200px - 200px = 1000px

Logo 寬度 = 60px + 120px(margin-right)= 180px
Nav 寬度 = (5個項目 × 150px) + 文字寬度(約350px)= 950px

總共需要 = 180px + 950px = 1130px

1130px > 1000px → Nav 會換行 ❌

解決方法:

  1. 加大可用寬度:min-width: 1400px → 可用寬度變 1200px

  2. 減少需要寬度:margin-right: 100px → Nav 寬度變 750px


inline-block 的間隙問題

問題:HTML 的換行會被瀏覽器當成「空白字符」,導致 inline-block 元素之間有約 4px 間隙。 developer.mozilla

HTML:

<div class="container">
  <div class="left">Left</div>  ← 這個換行變成空白
  <div class="right">Right</div>
</div>

視覺效果:

[Left] [4px間隙] [Right]  ← 導致寬度計算錯誤

解法(推薦):

.container {
  font-size: 0;  /* 消除空白字符 */
}

.left, .right {
  font-size: 16px;  /* 子元素恢復字體大小 */
}

學習階段對比

目前階段:Box Model(精確計算)

特點:

  • 需要手動計算每個元素寬度

  • 對齊方式複雜(vertical-align、line-height)

  • 響應式很難做

範例:

.header__logo-box {
  width: 60px;
  margin-right: 120px;  /* 固定數值 */
}

.header__nav-item {
  margin-right: 150px;  /* 固定數值 */
}

為什麼要學:

  • 理解寬度計算原理

  • 知道 inline-block 的限制在哪

  • 為 Flexbox 打基礎


未來階段:Flexbox(自動分配)

特點:

  • 不用計算寬度,瀏覽器自動處理

  • 自動垂直置中

  • 響應式友善

範例:

.header {
  display: flex;
  align-items: center;      /* 自動垂直置中 */
  justify-content: space-between;  /* 自動兩端對齊 */
}

/* 不需要計算寬度!不需要 line-height: 6.5! */

更未來:Grid(二維佈局)

特點:

  • 用比例分配空間

  • 精確控制二維佈局

  • 適合複雜版面

範例:

.header {
  display: grid;
  grid-template-columns: 200px 1fr;  /* Logo固定200px,Nav佔剩餘空間 */
  align-items: center;
}

1fr 的意思:「佔剩餘的所有空間」,完全不用計算!


最終完整程式碼

HTML

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>ZERO TYPE</title>
  </head>
  <body>
    <header class="header">
      <div class="header__logo-box">
        <div class="header__logo">
          <img class="header__logo-img" src="./public/logo.png" alt="網站 logo" />
        </div>
        <p class="header__logo-text">ZERO TYPE</p>
      </div>
      <nav class="header__nav">
        <ul class="header__nav-list">
          <li class="header__nav-item">Home</li>
          <li class="header__nav-item">Feature</li>
          <li class="header__nav-item">News</li>
          <li class="header__nav-item">About</li>
          <li class="header__nav-item">Contact</li>
        </ul>
      </nav>
    </header>

    <main class="main">
      <section class="hero">
        <div class="hero__intro">
          <h1 class="hero__title">IDEAS?</h1>
          <h2 class="hero__subtitle">This is just a placeholder.</h2>
          <p class="hero__text">
            You can remove any link to our website from this website template,
            you're free to use this website template without linking back to us.
          </p>
          <div class="hero__try-it">
            <button class="hero__try-it-btn">TRY IT NOW!</button>
            <p class="hero__try-it-hint">Don't worry it's for free.</p>
          </div>
        </div>
        <div class="hero__img-box">
          <img class="hero__img" src="./public/box.png" alt="網站主視覺圖片,一個裝滿各種不同類型檔案圖示的箱子" />
        </div>
      </section>

      <article class="content">
        <h2 class="content__title">This is just a placeholder.</h2>
        <p class="content__text">You can remove any link to our website from this website template, you're free to use this website template without linking back to us.</p>
        <p class="content__text">This is just a place holder, so you can see what the site would look like.</p>
        <p class="content__text">You can remove any link to our website from this website template, you're free to use this website template without linking back to us. This is just a place holder, so you can see what the site would look like.</p>
        <p class="content__text">You can remove any link to our website from this website template, you're free to use this website template without linking back to us.</p>
        <p class="content__text">This is just a place holder, so you can see what the site would look like.</p>
        <p class="content__text">You can remove any link to our website from this website template, you're free to use this website template without linking back to us. This is just a place holder, so you can see what the site would look like.</p>
      </article>
    </main>

    <footer class="footer">
      <div class="footer__copyright">&copy; 2023 Zerotype. All Rights Reserved.</div>
      <ul class="footer__social-media">
        <li class="footer__social-media-item">
          <a href="##" target="_blank" rel="noopener noreferrer">
            <img class="footer__social-media-icon" src="./public/facebook.png" alt="Facebook" />
          </a>
        </li>
        <li class="footer__social-media-item">
          <a href="##" target="_blank" rel="noopener noreferrer">
            <img class="footer__social-media-icon" src="./public/googleplus.png" alt="Google Plus" />
          </a>
        </li>
        <li class="footer__social-media-item">
          <a href="##" target="_blank" rel="noopener noreferrer">
            <img class="footer__social-media-icon" src="./public/twitter.png" alt="Twitter" />
          </a>
        </li>
        <li class="footer__social-media-item">
          <a href="##" target="_blank" rel="noopener noreferrer">
            <img class="footer__social-media-icon" src="./public/pinterest.png" alt="Pinterest" />
          </a>
        </li>
      </ul>
    </footer>
  </body>
</html>

CSS

/* 主要 Layout 版面架構要用父層結構完成 */
/* BEM 命名方式 */
/* 注意 tag 和 class 的權重,儘量用 class 命名各個元素 */
/* 要讓第一層內容直接撐開容器 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  min-width: 1300px;  /* 確保 Header 不會換行 */
}

/* ===== Header ===== */
.header {
  border: 5px solid black;
  margin-bottom: 30px;
  padding: 15px 50px 50px 150px;
  background-color: #f2f2f2;
}

.header__logo-box {
  display: inline-block;
  margin-right: 120px;
  text-align: center;
}

.header__logo {
  width: 60px;
  margin: 0 auto;
}

.header__logo-img {
  width: 100%;
  height: auto;
  vertical-align: bottom;  /* 消除圖片底部空隙 */
}

.header__logo-text {
  color: black;
  font-weight: bold;
  margin-top: -10px;
}

.header__nav {
  margin: 0;
  padding: 0;
  display: inline-block;
  text-align: right;
  line-height: 6.5;  /* 用行高做垂直對齊(魔術數字,未來用 Flexbox 改進)*/
  vertical-align: top;
}

.header__nav-list {
  padding: 0;
  margin: 0;
  list-style-type: none;
  display: inline-block;
  text-align: left;
}

.header__nav-item {
  color: gray;
  display: inline-block;
  margin-right: 120px;
}

.header__nav-item:last-child {
  margin-right: 0;
}

.header__nav-item:hover {
  color: orange;
  cursor: pointer;
}

/* ===== Main ===== */
.main {
  width: 70%;
  margin: 0 auto;
  margin-top: 30px;
  margin-bottom: 400px;
}

/* ===== Hero ===== */
.hero {
  background-color: #fcfcfc;
  height: 450px;
  margin-bottom: 250px;
  text-align: justify;  /* 兩端對齊 */
}

/* 用偽元素創造「第二行」讓 justify 生效 */
.hero::after {
  content: '';
  display: inline-block;
  width: 100%;
  height: 0;         /* 不佔高度 */
  overflow: hidden;
}

.hero__intro {
  display: inline-block;
  width: 50%;
  vertical-align: top;
  text-align: center;
}

.hero__title {
  font-size: 50px;
  line-height: 1.2;
  text-align: left;
  margin: 0;
}

.hero__subtitle {
  font-size: 30px;
  line-height: 1.2;
  text-align: left;
  margin: 0;
}

.hero__text {
  color: gray;
  font-size: 20px;
  line-height: 1.2;
  text-align: left;
  margin: 0;
}

.hero__try-it {
  display: inline-block;
  margin-top: 30px;
  text-align: left;
}

.hero__try-it-btn {
  font-size: 28px;
  display: block;
  padding: 20px 40px;
  background: linear-gradient(to bottom, #ffb84d 50%, #ff9500 50%);
  color: white;
  font-weight: bold;
  border: 0;
  border-radius: 5px;
  cursor: pointer;
}

.hero__try-it-hint {
  display: block;
  font-size: 16px;
  margin: 0;
  color: gray;
}

.hero__img-box {
  width: 45%;
  display: inline-block;
}

.hero__img {
  width: 100%;
  height: auto;
  vertical-align: bottom;  /* 消除圖片底部空隙 */
}

/* ===== Content ===== */
.content {
  column-count: 2;     /* 整個 article 分成兩欄 */
  column-gap: 20px;
}

.content__title {
  column-span: all;    /* 標題橫跨兩欄 */
  margin-bottom: 20px;
}

.content__text {
  color: gray;
  line-height: 1.2;
  break-inside: avoid;  /* 避免段落被切斷 */
  margin-top: 0;
  margin-bottom: 20px;
}

/* ===== Footer ===== */
.footer {
  border: 5px solid black;
  padding: 40px 150px 20px 100px;
  background-color: #f2f2f2;
  text-align: justify;
}

/* 用偽元素創造「第二行」讓 justify 生效 */
.footer::after {
  content: '';
  display: inline-block;
  width: 100%;
  height: 0;         /* 不佔高度 */
  overflow: hidden;
}

.footer__copyright {
  display: inline-block;
  vertical-align: middle;
}

.footer__social-media {
  margin: 0;         /* 重置 ul 預設樣式 */
  padding: 0;
  list-style-type: none;
  display: inline-block;
  vertical-align: middle;
}

.footer__social-media-item {
  display: inline-block;
  margin-right: 20px;
}

.footer__social-media-icon {
  width: 30px;
  display: block;
}

核心學習重點

1. BEM 命名帶來的價值

  • 權重扁平化:所有選擇器統一為 0-1-0,避免權重衝突

  • 語意清晰:一看 class 名稱就知道元素關係和身份

  • 易於維護:不會因為改一個地方影響其他地方

2. Box Model 的計算原理

  • 寬度公式:可用寬度 = 視窗寬 - padding,需要寬度 = 元素寬 + 間距

  • inline-block 特性:會換行(整個元素掉下去),有空白字符間隙

  • 必須精確計算:目前階段需要手動計算每個數值

3. 常見陷阱與解決方案

  • <ul> 預設樣式:必須重置 margin、padding、list-style

  • 圖片底部空隙:用 vertical-align: bottom 解決

  • text-align: justify 單行無效:用偽元素創造第二行

  • 偽元素佔高度:設定 height: 0 避免撐開容器

  • 段落分隔:用多個 <p> 而非 <br>,語意正確且對 SEO/無障礙友善

4. 未來的改進方向

  • Flexbox:自動分配空間,不用計算寬度,自動垂直置中

  • Grid:用比例分配空間,適合複雜二維佈局

  • 目前的痛點都會被解決:魔術數字、換行問題、對齊困難


參考資源

官方文件

  • MDN CSS 組織指南:介紹 OOCSS、BEM、SMACSS developer.mozilla

  • MDN CSS Specificity:權重計算規則 developer.mozilla

  • W3C HTML5 規範:段落與換行的正確用法

社群資源

  • CSS Specificity Calculator:https://specificity.keegan.st/ (線上計算工具)

  • BEM 官方文件:http://getbem.com

  • Airbnb JavaScript Style Guide:包含 CSS 寫作規範

延伸閱讀

  • BEM vs 巢狀選擇器的效能與權重分析 zoocha

  • Tailwind CSS 與 BEM 混用最佳實踐 wearecogworks

  • CSS 架構方法論比較 sitepoint