# Day 57:CSS Box Model 與偽元素、偽類 **Published by:** [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/) **Published on:** 2026-02-10 **URL:** https://paragraph.com/@gcake/day-57 ## Content 學習情境: 在檢視結訓學員的程式碼時,發現他們的切版作業採用了 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-boxContent-box (CSS 預設值): developer.mozilla實際佔據寬度 = width + padding-left + padding-right + border-left + border-right Border-box (現代推薦): developer.mozilla實際佔據寬度 = width (已包含 padding 和 border) 內容區域寬度 = width - padding - border CodePen 實驗:兩種模型對比

Content-box (預設)

width: 200px
padding: 20px
border: 5px

Border-box

width: 200px
padding: 20px
border: 5px

/* 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 區域
/* 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.mozilla2. 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。 vocus3. 按鈕區域

TRY IT NOW!

Don't worry it's for free.

/* CSS */ .try-it-btn h2 { padding: 20px 30px; background: linear-gradient(to bottom, #ffb84d 50%, #ff9500 50%); border-radius: 5px; /* 沒有設定 width,影響較小 */ } 影響:按鈕內容區會稍微縮小,但因為沒有固定寬度,視覺差異不大。 vocusCodePen 實驗:版面跑版問題

50% + 50% 佈局問題對比

❌ Content-box:會跑版

左欄 (content-box)
width: 50%
padding: 20px
border: 5px
右欄 (content-box)
width: 50%
padding: 20px
border: 5px

✅ Border-box:完美並排

左欄 (border-box)
width: 50%
padding: 20px
border: 5px
右欄 (border-box)
width: 50%
padding: 20px
border: 5px
/* 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 標籤不換行
左欄
右欄
左欄
右欄
替代方案:也可以用 float 來做多欄並排(後續會學習)。四、偽元素 (Pseudo-elements) 深入理解::before 和 ::after 是什麼?核心概念:透過 CSS 在元素內部插入虛擬內容,不需要修改 HTML。 developer.mozillaCodePen 實驗:偽元素基礎

偽元素示範

重要訊息
這是一段需要引號的文字內容,展示如何用偽元素自動加上裝飾性引號效果。這個方法可以保持 HTML 簡潔,所有裝飾都由 CSS 處理。
/* 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. 在元素內部,不是外部/* 結構示意 */
::before (第一個孩子) 原本的內容 ::after (最後一個孩子)
3. 無法用於空元素/* ❌ 這些元素無法使用偽元素 */ img::before { } /* 無效 */ input::after { } /* 無效 */ br::before { } /* 無效 */ /* 因為它們不能有子元素 */ 我的作業可以用偽元素優化的地方
/* 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 實驗:偽類與偽元素的組合

偽類 + 偽元素組合

帶動畫效果的連結
/* 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 語意 button::before { content: "★ "; } 六、Box-sizing 與偽元素的關係為什麼要設定偽元素的 box-sizing?偽元素會生成「真實的盒子」,所以需要設定 box-sizing。 forum.freecodecamp 偽類只是選擇器,不生成盒子,不需要設定。完整設定*, /* 選擇所有真實 HTML 元素 */ *::before, /* 選擇所有 ::before 偽元素 */ *::after { /* 選擇所有 ::after 偽元素 */ box-sizing: border-box; } 為什麼不能只寫 *?因為 * 只會選到真實的 HTML 元素,不會選到偽元素。 stackoverflowCodePen 實驗:偽元素的 box-sizing 問題

偽元素 box-sizing 對比

沒設定偽元素的 box-sizing

這是卡片內容

有設定偽元素的 box-sizing

這是卡片內容

/* 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)如果現在改變預設值,全世界數百萬個舊網站都會瞬間跑版。 redditCodePen 實驗:視覺化寬度差異

為什麼不能改預設值?

2005 年的網站設計師寫了這段 CSS:

.sidebar {
width: 300px;
padding: 20px;
border: 5px solid black;
}

✅ Content-box (當年的預設)

內容區
300px
padding 40px
border 10px

總寬度 = 350px

❌ 如果改成 Border-box

內容區
250px
(被壓縮)
padding 40px
border 10px

總寬度 = 300px

結果:同樣的 CSS,元素從 350px 變成 300px,所有基於「350px」的版面計算都會錯位。 這就是為什麼瀏覽器無法改變預設值——會破壞全球數百萬個舊網站。
/* 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年): redditwidth = content + padding + border (類似現在的 border-box) W3C CSS 標準 (2000年): developer.mozillawidth = content only (就是現在的 content-box) 當時 W3C 認為「寬度應該只指內容」符合邏輯,但實務上開發者發現 IE 的做法更直覺。 reddit諷刺的結果當時:大家罵 IE 不遵守標準 reddit現在:大家承認 IE 的設計比較合理,W3C 標準是錯誤決策 redditCSS 工作小組後來也公開承認「這是 CSS 的設計錯誤」。 reddit九、為什麼不能「現在就改」?1. Web 的核心哲學:永不破壞舊內容Tim Berners-Lee (Web 發明者) 的原則: reddit「1990 年的網頁,在 2026 年的瀏覽器中應該還能正常顯示」這是 Web 能成功的關鍵原因之一 — 內容可以跨越時空存在。2. 沒有「版本隔離」機制不像程式語言有版本宣告:// JavaScript 可以這樣 "use strict"; // 啟用嚴格模式 CSS 沒有類似機制能說「這個網頁用 CSS v2 標準」或「用 CSS v4 標準」。 reddit3. 改動成本太高如果改預設值,需要: reddit所有瀏覽器廠商同時更新全球開發者修改數百萬個網站測試確保沒有任何舊網站被破壞處理各種邊緣案例這個成本遠超過「讓開發者自己加三行 CSS」。十、特殊案例: