Day 57:CSS Box Model 與偽元素、偽類

學習情境: 在檢視結訓學員的程式碼時,發現他們的切版作業採用了 box-sizing: border-box,但我目前的作業還沒有使用這個設定。透過這次筆記深入理解 Box Model、偽元素、偽類的運作原理與最佳實踐。


一、問題發現:我的作業與學員程式碼的差異

我目前的 CSS 狀態

/* 我的作業沒有這段設定 */
header {
  border: 5px solid black;
  /* 使用預設的 content-box */
}

footer {
  border: 5px solid black;
  padding: 30px;
  /* 實際寬度會超出設定值 */
}

學員程式碼的常見寫法

/* 學員作業開頭有這段 */
* {
  box-sizing: border-box;
}

/* 搜尋後發現還有另外一種常用的包含三個選擇器的設定 */
*,
*::before,
*::after {
  box-sizing: border-box;
}

發現問題:為什麼大家都用這個設定?這三個選擇器分別是什麼意思?


二、Box Model 基礎概念

Content-box vs Border-box

Content-box (CSS 預設值): developer.mozilla

實際佔據寬度 = width + padding-left + padding-right + border-left + border-right

Border-box (現代推薦): developer.mozilla

實際佔據寬度 = width (已包含 padding 和 border)
內容區域寬度 = width - padding - border

CodePen 實驗:兩種模型對比

<!-- HTML -->
<div class="container">
  <div class="box content-box">
    <h3>Content-box (預設)</h3>
    <p>width: 200px<br>padding: 20px<br>border: 5px</p>
  </div>
  
  <div class="box border-box">
    <h3>Border-box</h3>
    <p>width: 200px<br>padding: 20px<br>border: 5px</p>
  </div>
</div>
/* CSS */
.container {
  display: flex;
  gap: 20px;
  padding: 20px;
  background: #f0f0f0;
}

.box {
  width: 200px;
  padding: 20px;
  border: 5px solid #333;
  background: white;
}

.content-box {
  box-sizing: content-box;
  /* 實際寬度 = 200 + 40 + 10 = 250px */
}

.border-box {
  box-sizing: border-box;
  /* 實際寬度 = 200px */
  /* 內容區 = 200 - 40 - 10 = 150px */
}

/* 視覺化實際寬度 */
.box::after {
  content: '';
  position: absolute;
  bottom: -30px;
  left: 0;
  right: 0;
  height: 2px;
  background: red;
}

觀察重點:

  • Content-box 的盒子明顯比較寬

  • Border-box 的盒子寬度就是你設定的 200px

  • 兩者內容區域大小不同


三、改用 Border-box 的影響分析

我的作業需要調整的地方

1. Header 區域

<!-- HTML -->
<header>
  <div class="logo">
    <img src="logo.png" alt="LOGO" />
    <p>ZERO TYPE</p>
  </div>
  <nav>
    <ul>
      <li><span>Home</span></li>
      <li>Feature</li>
      <li>News</li>
    </ul>
  </nav>
</header>
/* CSS - 影響分析 */
header {
  border: 5px solid black;
  background-color: #f2f2f2;
  /* content-box: 寬度 = 100% + 10px (border) */
  /* border-box: 寬度 = 100% (border 內推) */
}

header .logo {
  margin: 15px 80px 30px 150px;
  /* margin 不受 box-sizing 影響 */
}

影響:改用 border-box 後,header 內容區會縮小 10px(左右 border 各 5px)。 developer.mozilla

<!-- HTML -->
<footer>
  <div class="copyright">&copy; 2023 Zerotype.</div>
  <div class="social-media-link">
    <ul>
      <li><a href="#"><img src="facebook.png" alt="FB"/></a></li>
      <li><a href="#"><img src="twitter.png" alt="Twitter"/></a></li>
    </ul>
  </div>
</footer>
/* CSS - 影響分析 */
footer {
  border: 5px solid black;  /* 左右共 10px */
  padding: 30px;             /* 左右共 60px */
  background-color: #f2f2f2;
}

/* content-box 下:
   實際寬度 = 100% + 60px + 10px = 超出容器!
*/

/* border-box 下:
   實際寬度 = 100%
   內容區域 = 100% - 60px - 10px
*/

影響:改用 border-box 後,footer 內容區會縮小 70px,需要調整內部元素的 margin。 vocus

3. 按鈕區域

<!-- HTML -->
<div class="try-it-btn">
  <h2>TRY IT NOW!</h2>
  <p>Don't worry it's for free.</p>
</div>
/* CSS */
.try-it-btn h2 {
  padding: 20px 30px;
  background: linear-gradient(to bottom, #ffb84d 50%, #ff9500 50%);
  border-radius: 5px;
  /* 沒有設定 width,影響較小 */
}

影響:按鈕內容區會稍微縮小,但因為沒有固定寬度,視覺差異不大。 vocus

CodePen 實驗:版面跑版問題

<!-- HTML -->
<div class="layout-demo">
  <h3>50% + 50% 佈局問題對比</h3>
  
  <h4>❌ Content-box:會跑版</h4>
  <div class="row content-box-layout">
    <div class="col">左欄 (content-box)<br>width: 50%<br>padding: 20px<br>border: 5px</div><div class="col">右欄 (content-box)<br>width: 50%<br>padding: 20px<br>border: 5px</div>
  </div>
  
  <h4>✅ Border-box:完美並排</h4>
  <div class="row border-box-layout">
    <div class="col">左欄 (border-box)<br>width: 50%<br>padding: 20px<br>border: 5px</div><div class="col">右欄 (border-box)<br>width: 50%<br>padding: 20px<br>border: 5px</div>
  </div>
</div>
/* CSS */
.layout-demo {
  padding: 20px;
  max-width: 800px;
  margin: 0 auto;
  background: #f5f5f5;
}

h3 {
  margin-top: 0;
  color: #333;
}

h4 {
  margin: 20px 0 10px;
  color: #666;
}

.row {
  margin-bottom: 30px;
  border: 3px dashed #999;
  background: white;
  font-size: 0; /* ⭐ 消除 inline-block 的空白字符 */
}

.col {
  width: 50%;
  padding: 20px;
  border: 5px solid #333;
  display: inline-block;
  vertical-align: top;
  font-size: 14px; /* ⭐ 恢復正常字體大小 */
  line-height: 1.5;
}

/* Content-box 版本:會爆版! */
.content-box-layout .col {
  box-sizing: content-box;
  background: #ffebee;
  /* 每個欄位實際寬度 = 50% + 40px + 10px */
  /* 兩欄加起來 = 100% + 100px > 100% */
}

/* Border-box 版本:完美並排 */
.border-box-layout .col {
  box-sizing: border-box;
  background: #e8f5e9;
  /* 每個欄位實際寬度 = 50% (已包含 padding 和 border) */
  /* 兩欄加起來 = 100% */
}

觀察重點:

  • Content-box 版本的右欄會被擠到下一行

  • Border-box 版本完美佔據各 50%

⚠️ 重要提醒:inline-block 空白問題

使用 display: inline-block 時,HTML 標籤之間的換行和空白會被視為空白字符,佔據空間,導致元素換行。

解決方法一:父元素設 font-size: 0

.row {
  font-size: 0; /* 消除空白 */
}

.col {
  font-size: 14px; /* 子元素恢復正常 */
}

解決方法二:HTML 標籤不換行

<!-- ✅ 兩個 div 緊連,不要換行 -->
<div class="col">左欄</div><div class="col">右欄</div>

<!-- ❌ 這樣會產生空白字符 -->
<div class="col">左欄</div>
<div class="col">右欄</div>

替代方案:也可以用 float 來做多欄並排(後續會學習)。


四、偽元素 (Pseudo-elements) 深入理解

::before 和 ::after 是什麼?

核心概念:透過 CSS 在元素內部插入虛擬內容,不需要修改 HTML。 developer.mozilla

CodePen 實驗:偽元素基礎

<!-- HTML -->
<div class="demo-container">
  <h3>偽元素示範</h3>
  
  <div class="text-with-icon">重要訊息</div>
  
  <div class="quote">
    這是一段需要引號的文字內容,展示如何用偽元素自動加上裝飾性引號效果。這個方法可以保持 HTML 簡潔,所有裝飾都由 CSS 處理。
  </div>
  
  <ul class="nav-list">
    <li>Home</li>
    <li>About</li>
    <li>Contact</li>
  </ul>
</div>
/* CSS */
.demo-container {
  padding: 20px;
  max-width: 600px;
  margin: 0 auto;
  background: #f5f5f5;
}

h3 {
  margin-top: 0;
  color: #333;
}

/* 範例 1: 在文字前加圖示 */
.text-with-icon {
  padding: 15px;
  background: #fff3e0;
  border-left: 4px solid #ff9800;
  margin: 20px 0;
}

.text-with-icon::before {
  content: "⚠️ ";
  font-size: 1.2em;
}

/* 範例 2: 自動加引號 */
.quote {
  position: relative;
  padding: 20px 40px;
  background: white;
  margin: 20px 0;
  border-left: 4px solid #2196f3;
  line-height: 1.6;
  font-style: italic;
  color: #555;
}

.quote::before {
  content: "\201C";  /* 左彎引號 " (使用 Unicode 編碼) */
  position: absolute;
  top: -10px;        /* 往上移,不蓋住文字 */
  left: 10px;
  font-size: 3em;
  color: #2196f3;
  line-height: 1;
  opacity: 0.3;      /* 半透明,更柔和 */
}

.quote::after {
  content: "\201D";  /* 右彎引號 " */
  position: absolute;
  bottom: -10px;     /* 往下移,不蓋住文字 */
  right: 10px;
  font-size: 3em;
  color: #2196f3;
  line-height: 1;
  opacity: 0.3;
}

/* 範例 3: 導航列分隔符號 */
.nav-list {
  list-style: none;
  padding: 0;
  background: white;
  padding: 15px;
  border-radius: 4px;
}

.nav-list li {
  display: inline-block;
  padding: 5px 10px;
}

.nav-list li:not(:last-child)::after {
  content: "|";
  margin-left: 10px;
  color: #999;
}

觀察重點:

  • 所有裝飾都不在 HTML 中

  • ::before 在內容前面,::after 在內容後面

  • 必須有 content 屬性才會顯示 ppl-ai-file-upload.s3.amazonaws

  • 引號使用 Unicode 編碼 \201C 和 \201D 確保瀏覽器相容性

  • 調整 top/bottom 為負值讓引號不蓋住文字

💡 常用 Unicode 引號編碼:

符號

名稱

CSS content 寫法

"

左彎雙引號

content: "\201C";

"

右彎雙引號

content: "\201D";

'

左彎單引號

content: "\2018";

'

右彎單引號

content: "\2019";

「

中文左引號

content: "「"; 或 content: "\300C";

」

中文右引號

content: "」"; 或 content: "\300D";

偽元素的關鍵特性

1. 必須有 content 屬性

/* ❌ 不會顯示 */
.element::before {
  color: red;
  font-size: 2em;
}

/* ✅ 正確寫法 */
.element::before {
  content: "";  /* 就算是空的也要寫 */
  color: red;
  font-size: 2em;
}

2. 在元素內部,不是外部

/* 結構示意 */
<div class="box">
  ::before (第一個孩子)
  原本的內容
  ::after (最後一個孩子)
</div>

3. 無法用於空元素

/* ❌ 這些元素無法使用偽元素 */
img::before { }   /* 無效 */
input::after { }  /* 無效 */
br::before { }    /* 無效 */

/* 因為它們不能有子元素 */

我的作業可以用偽元素優化的地方

<!-- HTML (簡化版) -->
<header>
  <nav>
    <ul>
      <li><span>Home</span></li>
      <li>Feature</li>
      <li>News</li>
      <li>About</li>
    </ul>
  </nav>
</header>
/* CSS - 用偽元素加分隔線 */
nav li {
  display: inline-block;
  margin-right: 100px;
}

/* 優化:用偽元素取代 margin,視覺更清楚 */
nav li:not(:last-child)::after {
  content: "·";  /* 或用 "|" */
  margin: 0 20px;
  color: lightgray;
  font-size: 1.2em;
}

/* 現在選中狀態的底線效果 */
nav li span {
  color: orange;
  position: relative;
}

nav li span::after {
  content: "";
  display: block;
  width: 100%;
  height: 2px;
  background-color: orange;
  margin-top: 5px;
}

五、偽類 vs 偽元素:核心差異

概念對比表

特徵

偽類 :

偽元素 ::

作用

選擇元素的「特殊狀態」

創造「虛擬元素」

冒號數量

一個 :

兩個 ::

是否生成盒子

否

是

需要 content

不需要

::before/::after 必須有

數量限制

可多個

一個選擇器一組

常見用途

互動狀態、結構關係

裝飾性內容、特殊格式

CodePen 實驗:偽類與偽元素的組合

<!-- HTML -->
<div class="interactive-demo">
  <h3>偽類 + 偽元素組合</h3>
  
  <ul class="styled-list">
    <li>第一項</li>
    <li>第二項</li>
    <li>第三項</li>
  </ul>
  
  <a href="#" class="fancy-link">帶動畫效果的連結</a>
</div>
/* CSS */
.interactive-demo {
  padding: 20px;
  max-width: 600px;
  margin: 0 auto;
}

/* 範例 1: 偽類選擇結構 */
.styled-list {
  list-style: none;
  padding: 0;
}

.styled-list li {
  padding: 10px;
  margin: 5px 0;
  background: #f0f0f0;
}

/* 用偽類選擇第一個 */
.styled-list li:first-child {
  background: #e3f2fd;
  font-weight: bold;
}

/* 用偽元素加裝飾 */
.styled-list li:first-child::before {
  content: "👑 ";
}

/* 用偽類選擇懸停狀態 */
.styled-list li:hover {
  background: #fff3e0;
  cursor: pointer;
}

/* 範例 2: 動態底線效果 */
.fancy-link {
  display: inline-block;
  padding: 10px 20px;
  text-decoration: none;
  color: #333;
  position: relative;
}

/* 用偽元素創建底線 */
.fancy-link::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0;
  height: 2px;
  background: orange;
  transition: width 0.3s;
}

/* 用偽類觸發動畫 */
.fancy-link:hover::after {
  width: 100%;
}

觀察重點:

  • 偽類 :first-child 選擇「第一個元素」(狀態)

  • 偽元素 ::before 插入圖示(新內容)

  • 偽類 :hover 觸發「懸停狀態」

  • 偽元素 ::after 創造底線(新元素)

  • 可以組合使用:li:hover::before

設計理念差異

偽類:避免手動管理狀態

// 如果沒有偽類,需要這樣寫 JS
button.addEventListener('mouseenter', () => {
  button.classList.add('hovered');
});
button.addEventListener('mouseleave', () => {
  button.classList.remove('hovered');
});

// 有了 :hover 偽類,CSS 自動處理
button:hover {
  background: orange;
}

偽元素:避免污染 HTML 語意

<!-- 沒有偽元素:HTML 很亂 -->
<button>
  <span class="icon">★</span>
  <span class="text">按鈕</span>
</button>

<!-- 有了偽元素:HTML 簡潔 -->
<button>按鈕</button>
button::before {
  content: "★ ";
}

六、Box-sizing 與偽元素的關係

為什麼要設定偽元素的 box-sizing?

偽元素會生成「真實的盒子」,所以需要設定 box-sizing。 forum.freecodecamp 偽類只是選擇器,不生成盒子,不需要設定。

完整設定

*,           /* 選擇所有真實 HTML 元素 */
*::before,   /* 選擇所有 ::before 偽元素 */
*::after {   /* 選擇所有 ::after 偽元素 */
  box-sizing: border-box;
}

為什麼不能只寫 *?

因為 * 只會選到真實的 HTML 元素,不會選到偽元素。 stackoverflow

CodePen 實驗:偽元素的 box-sizing 問題

<!-- HTML -->
<div class="comparison">
  <h3>偽元素 box-sizing 對比</h3>
  
  <div class="card without-reset">
    <h4>沒設定偽元素的 box-sizing</h4>
    <p>這是卡片內容</p>
  </div>
  
  <div class="card with-reset">
    <h4>有設定偽元素的 box-sizing</h4>
    <p>這是卡片內容</p>
  </div>
</div>
/* CSS */
.comparison {
  padding: 20px;
}

.card {
  width: 300px;
  padding: 20px;
  border: 2px solid #333;
  margin: 20px 0;
  position: relative;
  background: white;
}

/* 兩張卡片都用 border-box */
.card {
  box-sizing: border-box;
}

/* 共用的偽元素樣式 */
.card::before {
  content: "標籤";
  position: absolute;
  top: -15px;
  left: 20px;
  width: 60px;
  padding: 5px 10px;
  border: 2px solid orange;
  background: white;
  font-size: 12px;
  text-align: center;
}

/* 第一張卡片:偽元素用預設 content-box */
.without-reset::before {
  box-sizing: content-box;
  /* 實際寬度 = 60 + 20 + 4 = 84px */
}

/* 第二張卡片:偽元素也用 border-box */
.with-reset::before {
  box-sizing: border-box;
  /* 實際寬度 = 60px */
}

/* 視覺化寬度差異 */
.card::before {
  outline: 2px dashed red;
  outline-offset: 2px;
}

觀察重點:

  • 第一張卡片的標籤明顯比較寬

  • 第二張卡片的標籤寬度就是設定的 60px

  • 如果不統一 box-sizing,版面計算會混亂 stackoverflow

偽類不需要設定 box-sizing

/* ❌ 偽類不用設定 */
a:hover {
  /* :hover 只是選擇「懸停狀態的 a 元素」
     這個 a 元素本身已經有 box-sizing */
}

/* ✅ 偽元素需要設定 */
a::before {
  /* ::before 生成了新的盒子
     需要明確設定 box-sizing */
}

原因: developer.mozilla

  • 偽類只是「選擇器的條件」,不生成盒子

  • 偽元素會生成「新的盒子」,需要設定 forum.freecodecamp


七、為什麼預設值還是 Content-box?

核心原因:向後兼容性 (Backward Compatibility)

如果現在改變預設值,全世界數百萬個舊網站都會瞬間跑版。 reddit

CodePen 實驗:視覺化寬度差異

<!-- HTML -->
<div class="simple-demo">
  <h3>為什麼不能改預設值?</h3>
  
  <p class="intro">
    2005 年的網站設計師寫了這段 CSS:
  </p>
  
  <div class="code-block">
    .sidebar {<br>
    &nbsp;&nbsp;width: 300px;<br>
    &nbsp;&nbsp;padding: 20px;<br>
    &nbsp;&nbsp;border: 5px solid black;<br>
    }
  </div>
  
  <div class="comparison">
    <div class="col">
      <h4>✅ Content-box (當年的預設)</h4>
      <div class="visual-box content-box-visual">
        <div class="content-area">內容區<br>300px</div>
        <div class="padding-area">padding 40px</div>
        <div class="border-area">border 10px</div>
      </div>
      <p class="total">總寬度 = <strong>350px</strong></p>
    </div>
    
    <div class="col">
      <h4>❌ 如果改成 Border-box</h4>
      <div class="visual-box border-box-visual">
        <div class="content-area small">內容區<br>250px<br>(被壓縮)</div>
        <div class="padding-area">padding 40px</div>
        <div class="border-area">border 10px</div>
      </div>
      <p class="total">總寬度 = <strong>300px</strong></p>
    </div>
  </div>
  
  <div class="note">
    <strong>結果:</strong>同樣的 CSS,元素從 350px 變成 300px,所有基於「350px」的版面計算都會錯位。
    這就是為什麼瀏覽器無法改變預設值——會破壞全球數百萬個舊網站。
  </div>
</div>
/* CSS */
.simple-demo {
  padding: 20px;
  max-width: 900px;
  margin: 0 auto;
  background: #f5f5f5;
}

h3 {
  margin-top: 0;
  color: #333;
  text-align: center;
}

h4 {
  margin: 10px 0;
  color: #666;
  text-align: center;
}

.intro {
  background: #e3f2fd;
  padding: 15px;
  border-left: 4px solid #2196f3;
  line-height: 1.8;
  margin-bottom: 20px;
  text-align: center;
}

.code-block {
  background: #263238;
  color: #aed581;
  padding: 15px;
  border-radius: 4px;
  font-family: monospace;
  margin-bottom: 30px;
  line-height: 1.6;
}

.comparison {
  display: flex;
  gap: 20px;
  margin-bottom: 30px;
}

.col {
  flex: 1;
}

.visual-box {
  border: 3px solid #333;
  margin: 20px 0;
  padding: 5px;
  background: white;
}

.content-box-visual {
  width: 350px;
}

.border-box-visual {
  width: 300px;
}

.content-area {
  background: #e8f5e9;
  padding: 40px 20px;
  text-align: center;
  font-weight: bold;
  border: 5px solid #4caf50;
}

.content-area.small {
  background: #ffebee;
  border-color: #f44336;
}

.padding-area,
.border-area {
  text-align: center;
  padding: 5px;
  font-size: 12px;
  color: #666;
}

.total {
  text-align: center;
  font-size: 18px;
  background: #fff9c4;
  padding: 10px;
  margin: 10px 0;
  border-radius: 4px;
}

.total strong {
  color: #1976d2;
  font-size: 24px;
}

.note {
  padding: 20px;
  background: #fff9c4;
  border: 3px solid #fbc02d;
  border-radius: 4px;
  line-height: 1.8;
}

.note strong {
  color: #f57c00;
}

觀察重點:

  • 左側 (content-box):側邊欄實際佔據 350px,這是設計師當年的預期

  • 右側 (border-box):側邊欄實際佔據 300px,內容區被壓縮 50px

  • 同樣的 CSS 代碼,在不同 box-sizing 下,元素佔據的空間完全不同

Web 標準的兩難

想像一下這個情境: reddit

/* 2005 年寫的舊網站,沒有設定 box-sizing */
.sidebar {
  width: 300px;
  padding: 20px;
  border: 5px solid black;
  /* 作者預期:實際寬度 = 300 + 40 + 10 = 350px */
}

.content {
  width: 650px;  /* 剛好跟 sidebar 加起來是 1000px */
}

如果今天瀏覽器把預設值改成 border-box: reddit

  • .sidebar 實際寬度變成 300px(不是 350px)

  • 原本設計好的版面全部跑掉

  • 全球數百萬個沒在維護的舊網站集體崩潰

受影響的規模

這些地方還在使用 20 年前的網頁系統: reddit

  • 政府機關網站

  • 醫療機構系統

  • 大型企業內部工具

  • 教育機構平台

  • 銀行系統介面

它們可能永遠不會更新,但必須繼續運作。


八、歷史趣聞:IE 反而做對了

W3C vs Internet Explorer

早期有兩套盒模型標準: blog.csdn

Internet Explorer 5 (1999年): reddit

width = content + padding + border
(類似現在的 border-box)

W3C CSS 標準 (2000年): developer.mozilla

width = content only
(就是現在的 content-box)

當時 W3C 認為「寬度應該只指內容」符合邏輯,但實務上開發者發現 IE 的做法更直覺。 reddit

諷刺的結果

  • 當時:大家罵 IE 不遵守標準 reddit

  • 現在:大家承認 IE 的設計比較合理,W3C 標準是錯誤決策 reddit

CSS 工作小組後來也公開承認「這是 CSS 的設計錯誤」。 reddit


九、為什麼不能「現在就改」?

1. Web 的核心哲學:永不破壞舊內容

Tim Berners-Lee (Web 發明者) 的原則: reddit

「1990 年的網頁,在 2026 年的瀏覽器中應該還能正常顯示」

這是 Web 能成功的關鍵原因之一 — 內容可以跨越時空存在。

2. 沒有「版本隔離」機制

不像程式語言有版本宣告:

// JavaScript 可以這樣
"use strict";  // 啟用嚴格模式

CSS 沒有類似機制能說「這個網頁用 CSS v2 標準」或「用 CSS v4 標準」。 reddit

3. 改動成本太高

如果改預設值,需要: reddit

  • 所有瀏覽器廠商同時更新

  • 全球開發者修改數百萬個網站

  • 測試確保沒有任何舊網站被破壞

  • 處理各種邊緣案例

這個成本遠超過「讓開發者自己加三行 CSS」。


十、特殊案例:<button> 預設是 border-box

有趣的是,某些 HTML 元素的預設值其實是 border-box: stackoverflow

/* 瀏覽器內建樣式 */
button, input[type="submit"], input[type="reset"] {
  box-sizing: border-box;  /* 預設就是 border-box! */
}

原因: stackoverflow

  • 這些元素是後來才加入 HTML 的

  • 設計時已經知道 border-box 比較好

  • 不會破壞既有網站(因為是新元素)

這證明了「如果重新設計,大家都會選 border-box」。


十一、現代解決方案:CSS Reset

既然標準無法改,社群發展出「重置樣式」的做法: freecodecamp

方案一:全域重置(最常見)

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

方案二:繼承式重置

html {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;  /* 繼承父元素的設定 */
}

好處:可以在特定區域改回 content-box。 freecodecamp

方案三:使用 CSS 框架

現代框架都內建 border-box:

  • Bootstrap

  • Tailwind CSS

  • Normalize.css


十二、類似的「歷史包袱」案例

JavaScript 的 typeof null

typeof null  // "object" (錯誤!)

這是 JavaScript 設計錯誤,但無法修正,因為會破壞全球數百萬個網站。 reddit

HTTP 狀態碼 418

HTTP 418: I'm a teapot

原本是愚人節玩笑,但被寫入標準後就無法移除,因為有人真的在用。 reddit


十三、統一 Box-sizing 的重要性

核心原則

只要元素會產生盒子並參與版面佈局,就需要統一計算方式。 dev

問題情境:兩欄佈局

/* 混用兩種 box-sizing */
.left-column {
  width: 50%;
  padding: 20px;
  border: 5px solid black;
  box-sizing: content-box;
  /* 實際寬度 = 50% + 40px + 10px → 爆版! */
}

.right-column {
  width: 50%;
  padding: 20px;
  border: 5px solid black;
  box-sizing: border-box;
  /* 實際寬度 = 50% (已包含) → 正常 */
}
/* 結果:兩欄加起來超過 100%,右欄被擠下去 */

統一後的效果

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

.left-column,
.right-column {
  width: 50%;
  padding: 20px;
  border: 5px solid black;
  /* 兩欄各佔 50%,完美並排 */
}

實務價值

  1. 直覺化計算: crypist

    • 設定 width: 300px,元素在版面上就佔 300px

  2. 響應式設計更容易: dev

    .col {
      width: 25%;  /* 不用擔心 padding/border 爆版 */
      padding: 15px;
      border: 2px solid gray;
    }
    
  3. 維護性提升: crypist

    • 修改 padding 或 border 時不需要重新計算 width


十四、現代最佳實踐

新專案必做

/* 專案第一行 CSS */
*,
*::before,
*::after {
  box-sizing: border-box;
}

維護舊專案時

評估風險後再決定是否加上:

  • 測試所有頁面

  • 檢查是否有地方依賴 content-box 行為

  • 漸進式重構,不要一次全改


十五、我的作業改版計劃

第一步:加入全域設定

/* 在 style.css 最前面加上 */
*,
*::before,
*::after {
  box-sizing: border-box;
}

第二步:測試版面影響

需要檢查這些區域:

  1. Header:

    • 檢查內容是否太擠

    • logo 位置是否正常

  2. Footer:

    • 檢查 padding 是否需要調整

    • 社交媒體連結排列是否正常

  3. Main 區域:

    • .hero-intro 和 .hero-img 的並排

    • Article 的雙欄排版

第三步:用偽元素優化

/* 導航列加分隔線 */
header nav li:not(:last-child)::after {
  content: "·";
  margin: 0 15px;
  color: #ccc;
}

/* TRY IT NOW 按鈕加箭頭 */
.try-it-btn h2::after {
  content: " →";
  transition: transform 0.3s;
}

.try-it-btn h2:hover::after {
  transform: translateX(5px);
}

/* Footer 上方加裝飾線 */
footer::before {
  content: "";
  display: block;
  width: 80%;
  height: 2px;
  background: linear-gradient(
    to right,
    transparent,
    #ff9500,
    transparent
  );
  margin: 0 auto 20px;
}

十六、重點整理與易錯提醒

Box Model

記住:

  • Border-box 讓寬度計算更直覺

  • 現代專案一律使用 border-box

  • 改動後要測試所有版面

易錯:

  • 忘記 border-box 會壓縮 content 區域

  • 只設定 *,忘記偽元素

  • 混用兩種 box-sizing

偽元素

記住:

  • 兩個冒號 ::

  • 必須有 content 屬性

  • 在元素內部,不是外部

  • 適合裝飾性內容

  • 引號用 Unicode 編碼 \201C \201D

易錯:

  • 忘記 content: "";

  • 用在空元素(<img>, <input>)

  • 誤以為在元素外面

  • 引號位置蓋住文字

偽類

記住:

  • 一個冒號 :

  • 選擇元素的狀態或結構關係

  • 不生成盒子

易錯:

  • 混淆冒號數量

  • 對偽類設定 box-sizing

統一 box-sizing

記住:

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

❌ 易錯:

  • 只寫 *

  • 忘記偽元素


十七、關鍵啟示

這個案例告訴我們: reddit

  1. 標準制定要謹慎:一旦發佈就很難改

  2. 向後兼容很重要:比「技術上正確」更重要

  3. 開發者可以自救:透過 Reset CSS 解決標準問題

  4. 歷史會評價設計:20 年後才發現當初的決策是錯的

CSS Working Group 現在每次制定新標準都會討論「這會不會成為下一個 box-sizing」。 reddit


十八、延伸學習資源

MDN 官方文件

CodePen 實驗

我會在這裡貼上自己的實驗連結:

  • [ ] Box Model 對比實驗 https://codepen.io/gcake119/pen/YPWRJZJ

  • [ ] 偽元素應用範例 https://codepen.io/gcake119/pen/EayOMje

  • [ ] 偽元素的 box-sizing 問題 https://codepen.io/gcake119/pen/gbMQERM

  • [ ] 偽分類與偽元素結合 https://codepen.io/gcake119/pen/xbOQBqB

  • [ ] 歷史包袱視覺化示範 https://codepen.io/gcake119/pen/YPWRgOq


十九、學習心得

關鍵啟示

  1. 標準不等於最佳實踐

    • Content-box 是標準,但 border-box 更實用

    • 要理解背後的歷史脈絡

  2. 向後兼容很重要

    • Web 的成功建立在「永不破壞舊內容」

    • 這也是為什麼有些設計無法改變

  3. 社群可以自救

    • 雖然標準有問題,社群透過 CSS Reset 解決

    • 現代專案幾乎都採用 border-box

  4. 偽元素是強大工具

    • 保持 HTML 簡潔

    • 裝飾性內容用 CSS 處理

    • 減少無意義的標籤

  5. 實作中學習最有效

    • CodePen 實驗幫助理解抽象概念

    • 親手重現問題才能真正掌握

下一步行動

  • [ ] 將 border-box 加入我的作業

  • [ ] 測試所有版面是否正常

  • [ ] 用偽元素優化導航列和按鈕

  • [ ] 在 CodePen 建立實驗範例

  • [ ] 整理成技術筆記並分享


筆記日期: 2026-02-10
學習情境: 對照結訓學員程式碼發現的問題
主題標籤: #CSS #BoxModel #Pseudo-elements #Pseudo-classes #BestPractice
CodePen 實驗: 連結如上文