讀書小組的網頁應在視窗收窄、文字放大或內容改變時保持易讀。樣式設計要回答三個問題:規則選中了誰、哪項宣告決定屬性、盒子如何分配空間。理解這些關係,才能解釋不同條件下的排版。
CSS 提供樣式規則。Bootstrap 整理了常用的排版、工具類別及元件規則。理解底層 CSS,才能合理組合它們,並找出衝突原因。
規則選取元素並指定屬性
定義
CSS 規則與宣告
CSS 規則由選擇器與宣告區塊組成。選擇器配對元素,每項宣告指定一個屬性及其值。
.reading-card {
color: #20354d;
line-height: 1.6;
padding: 1rem;
}
.reading-card 配對 class 清單包含 reading-card 的元素。三項宣告設定文字顏色、行高、內邊距。屬性名稱拼作 color;分號分隔宣告,大括號界定區塊。
CSS 可放在由 link 連結的外部樣式表、style 元素或元素的 style 屬性中。外部樣式表便於多頁共用;style 適合小型獨立示例;行內樣式直接作用於單一元素。
選擇器可以描述不同關係:
| 選擇器 | 配對對象 |
|---|---|
p | 段落元素 |
.note | 帶有 note 類別的元素 |
#next-session | 使用該 ID 的元素 |
.reading-card p | 閱讀卡片之內的段落後代 |
.reading-card > h2 | 直接隸屬閱讀卡片的標題 |
.note.highlight | 同時帶有兩個類別的元素 |
a:hover | 處於滑鼠懸停狀態的連結 |
a:focus-visible | 瀏覽器判定應顯示可見焦點狀態的連結 |
li:nth-child(2) | 在父元素內排行第二的清單項目 |
p::first-letter | 適用時,段落所呈現內容的首字母部分 |
空格表示後代,.note.highlight 則要求同一元素同時有兩個類別。逗號組合獨立選擇器,例如 h1, h2 配對兩種標題。偽類別描述狀態或關係;偽元素識別呈現內容的一部分。
層疊與繼承回答不同問題
層疊決定元素同一屬性的多項競爭宣告之中,哪項勝出。繼承則在元素沒有從層疊取得自身值時,為可繼承屬性提供值。文字顏色及字體家族通常會繼承;內邊距、邊框與外邊距通常不會。
在本單元使用的一般、未分層作者樣式表規則中,先比較選擇器的特異性。依次計算 ID、類別/屬性/偽類別,以及元素名稱/偽元素的數目,再按此順序比較;不能把它們當作可互換的分數相加。特異性相同時,較後出現的宣告勝出。一般的行內作者樣式優先於一般樣式表規則。
常見錯誤
把樣式表位置當作優先規則
外部樣式表與 style 元素同屬作者來源。在上述一般、未分層條件下,把規則放入 style 不會自動提高優先次序;仍須比較特異性及原始碼順序。
!important 會改變層疊的重要性層級,並不代表它能勝過任何可能的宣告。來源、層、重要宣告、動畫與過渡效果另有排序規則。
例題
開啟瀏覽器前先預測顏色
<section class="reading-card">
<p class="note">帶來一條關於文章的問題。</p>
</section>
body { color: #20354d; font-family: system-ui, sans-serif; }
.note { color: navy; }
.reading-card p { color: teal; }
.reading-card .note { color: maroon; }
.note { color: purple; }
四個顏色選擇器都配對該段落,其特異性依次為 (0,1,0)、(0,1,1)、(0,2,0) 和 (0,1,0)。段落呈栗色,因為比較原始碼順序之前,(0,2,0) 已經勝出。若只有兩條 .note 規則競爭,最後一條會勝過前一條;但它不能勝過更具特異性的選擇器。
段落繼承 body 的字體家族,但顏色由直接配對它的宣告決定。祖先選擇器的特異性不會令繼承值勝過子元素自身的宣告。
Styles 面板顯示配對與落敗的宣告;Computed 面板顯示最終計算值及繼承來源,可據此追查樣式。
數值描述文字、顏色與尺寸
font-family 提供有先後次序的字體選項。system-ui, sans-serif 使用系統已有的字體,不下載字體檔案。font-size、font-weight、line-height 與 text-align 控制文字的不同方面。沒有單位的行高,例如 1.6,會隨元素自身的字體大小縮放。
本地網頁字體透過 @font-face 宣告,並在 font-family 中引用;字體檔案須存在於指定 URL。前處理器把其他來源語言編譯成 CSS;原生自訂屬性(如 --accent)則直接透過 var(--accent) 讀取。
| 數值或單位 | 解讀方式 |
|---|---|
16px | 十六個 CSS 像素;不一定等於螢幕的實體像素 |
1rem | 根元素的字體大小 |
1em | 元素的字體大小;用於 font-size 本身時,則以父元素字體大小為基準 |
寬度的 50% | 適用包含區塊寬度的一定比例 |
50vw | 視窗寬度的一半 |
50vh | 視窗高度的一半 |
#245a92 | 以十六進位紅、綠、藍色彩通道表示的顏色 |
rgb(36 90 146) | 同一組 RGB 通道數值的函數寫法 |
hsl() 提供另一種顏色表示法。百分比的意思取決於設定的屬性,應先確認其基準,不應一律當作「螢幕的百分比」。最大及最小尺寸可限制彈性尺寸,而不必令所有元素都採用固定寬度。
盒模型令寬度計算明確
定義
內容、內邊距、邊框與外邊距
內容盒容納元素內容。內邊距位於內容與邊框之間,邊框包圍內邊距,外邊距則在邊框以外。背景通常會繪畫至內邊距及邊框下方;外邊距是透明的。
例題
計算卡片的水平尺寸
.reading-card {
box-sizing: content-box;
width: 240px;
padding: 12px;
border: 2px solid #60758b;
margin: 16px;
}
採用 content-box 時,width 描述內容寬度。因此邊框盒寬度是 CSS 像素。再計入兩側外邊距,水平佔用範圍就是 CSS 像素。
若只把 box-sizing 改成 border-box,宣告的 240px 便包含內邊距及邊框。內容寬度變成 CSS 像素;連同外邊距的水平範圍則是 CSS 像素。兩種情況下,外邊距都在宣告寬度之外。
四值簡寫 padding: 8px 12px 16px 20px 依次表示上、右、下、左。兩個值表示垂直方向、再水平方向;一個值則用於四邊。margin 採用同一排列次序。
水平計算不能機械地套用至所有垂直情況:一般區塊盒的垂直外邊距可能合併。內邊距、邊框及排版間隙有不同的行為。解釋空間差異前,應先檢查盒模型。
顯示方式與文件流決定盒子的參與方式
在一般文件流中,區塊盒從新一行開始;寬度為自動值時,通常填滿可用的行內空間。行內內容則參與文字行的排版。span 這類非取代行內元素,不會像區塊盒一樣接受寬高;行內圖像是取代元素,則可以有尺寸。
inline-block 把盒子放在文字行內,同時容許明確寬高。display: flex 令直接子元素成為彈性項目,排列方式由彈性容器控制。改變顯示方式,不會改變 HTML 的語義:導覽仍可是一份連結清單,同時讓項目水平排列。
.reading-nav {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
list-style: none;
padding: 0;
}
這裏清單的直接子元素形成可換行的一列,並保留一致間隙。浮動則有另一種用途:相鄰行內內容可以環繞浮動盒。clear 決定後續盒子須移到哪些浮動盒之下;display: flow-root 則可建立包含其浮動內容的容器。
display: none 令元素不再產生參與排版的盒子。visibility: hidden 隱藏盒子,但保留其排版空間。兩者影響附近內容的方式不同,應按期望的排版行為選擇。
定位需要參照盒
position 值 | 基本行為 |
|---|---|
static | 使用一般定位;偏移值不會令它重新定位 |
relative | 套用偏移,但保留原有排版位置 |
absolute | 脫離一般文件流,在其包含區塊內定位 |
fixed | 在沒有相關變形的簡單文件內,以視窗作定位基準 |
sticky | 到達指定偏移位置後,在捲動與包含區塊的限制內移動 |
.session-card { position: relative; padding: 1rem; }
.session-label { position: absolute; top: 0.5rem; right: 0.5rem; }
本例中,相對定位的卡片建立了絕對定位標籤的包含區塊。標籤不佔用一般文件流中的位置,因此卡片內容需要為標籤預留足夠空間。較複雜的文件中,變形等其他特徵也可能建立包含區塊。解讀 top 或 left 前,先找出實際參照盒。
黏性標題需要 top: 0 這類偏移值,以及足夠的捲動空間。定位適合表達局部關係;頁面的主要欄位,則更適合用彈性排版或網格等系統描述。
先建立窄版,再按空間擴充
響應式設計使用同一份內容結構,按可用空間調整呈現方式。應明確加入視窗宣告,讓文字換行、限制閱讀寬度,並讓圖像隨容器縮放。
例題
只用本地 CSS 的完整響應式頁面
這份文件內含樣式表,可儲存為 reading.html。窄視窗下兩區上下排列;從 48rem 起,兩區並排。
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>讀書小組:下一次討論</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font: 1rem/1.6 system-ui, sans-serif;
color: #20354d;
background: #f4f6fa;
}
header, main, footer {
width: calc(100% - 2rem);
max-width: 64rem;
margin-inline: auto;
}
.session-layout { display: flex; flex-direction: column; gap: 1rem; }
.reading-card {
padding: 1rem;
border: 1px solid #b8c7d8;
border-radius: 0.5rem;
background: white;
}
.reading-card h2 { line-height: 1.2; }
@media (min-width: 48rem) {
.session-layout { flex-direction: row; }
.main-topic { flex: 2; }
.side-note { flex: 1; }
}
</style>
</head>
<body>
<header><h1>讀書小組</h1></header>
<main class="session-layout">
<section class="reading-card main-topic">
<h2>下一次討論</h2>
<p>比較兩種組織短篇技術文章的方式。</p>
</section>
<aside class="reading-card side-note">
<h2>事前準備</h2>
<p>帶來一段文字和一條問題。</p>
</aside>
</main>
<footer><p>討論於 16:00 開始。</p></footer>
</body>
</html>
基礎規則先建立窄版;媒體查詢在條件成立時加入闊版,而不是複製另一份內容。flex: 2 與 flex: 1 使用不同增長因子分配剩餘空間,內邊距與邊框也會影響最終可見盒子的尺寸。
點陣圖像可使用 max-width: 100%; height: auto,在容器收窄時縮小,同時保留固有長寬比例。picture 亦可為較闊視窗選擇另一種構圖。測試時,應檢查每個排版變化點兩側的寬度,再嘗試較長文字與較大的瀏覽器文字設定。
Bootstrap 為可重用排版規則命名
以下類別例子使用 Bootstrap 5.3 慣例。本地文件可以先載入本地框架樣式表,再載入自己的樣式表:
<link rel="stylesheet" href="vendor/bootstrap.min.css">
<link rel="stylesheet" href="reading.css">
這些本地檔案須先備妥。Bootstrap CSS 包含 Reboot 基礎樣式、網格、工具類別及元件樣式;類別名稱須配合相應樣式表才會產生外觀。
Bootstrap 預設網格在每一列使用十二個尺寸單位。容器界定內容區,列組織欄位與欄間距,欄位類別則分配寬度。這不要求十二個內容元素,而容器本身也不會自動建立一列欄位。
<main class="container py-4">
<div class="row g-3">
<section class="col-12 col-md-8">
<div class="border rounded p-3">
<h2>下一次討論</h2>
<p>閱讀開首一節,指出其主要論點。</p>
</div>
</section>
<aside class="col-12 col-md-4">
<div class="border rounded p-3">
<h2>事前準備</h2>
<p>帶來一條問題。</p>
</div>
</aside>
</div>
</main>
低於 md 斷點時,每區使用一列的 12/12,因此上下排列。從 md 起,兩區分別使用 8/12 與 4/12。g-3 提供欄間距;邊框與內邊距放在內層盒子上,讓可見表面之間保留該間距。響應式類別會在更闊視窗繼續適用,直至另一條符合條件的較後斷點規則改變設定。
| 預設層級 | 最小視窗寬度 | 例子 |
|---|---|---|
| 特小 | 無最小值 | col-12 |
sm | 576 CSS 像素 | col-sm-6 |
md | 768 CSS 像素 | col-md-8 |
lg | 992 CSS 像素 | col-lg-4 |
xl | 1200 CSS 像素 | col-xl-3 |
xxl | 1400 CSS 像素 | col-xxl-2 |
這些類別名稱沒有 xs 中綴。.container 具有響應式最大寬度,.container-fluid 則保持全寬。單純的 .col 與同級欄位平均分享可用寬度;.col-auto 按內容決定尺寸。可換行列中,明確指定的欄位總跨度超過十二時,較後的欄位會移到下一行。
工具類別與元件仍需要語義 HTML
工具類別讓細小規則可重用。在 Bootstrap 預設間距比例中,p-3 提供 1rem 內邊距,mb-3 提供 1rem 下外邊距,gap-3 則在適用排版中提供 1rem 間隙。p-2 p-md-4 讓內邊距在 md 改用另一級設定。Bootstrap 5 的 ms-* 與 me-* 分別代表起始及末端間距;fst-italic 選擇斜體。
HTML 中的類別順序不是優先清單:class="p-2 p-3" 不表示「最後一個類別勝出」。結果由樣式表宣告決定。許多工具類別帶有 !important,所以單把自訂 CSS 放在後面,未必足以覆寫它們。每個生效設定應選定一個工具類別,再以元件自訂屬性或範圍合適的規則調整外觀。
<nav class="nav nav-pills" aria-label="閱讀章節">
<a class="nav-link active" aria-current="location" href="#plan">計劃</a>
<a class="nav-link" href="#preparation">事前準備</a>
</nav>
<section id="plan"><h2>計劃</h2></section>
<section id="preparation"><h2>事前準備</h2></section>
<button type="button" class="btn btn-primary">標記為待閱讀</button>
按鈕類別為真正的按鈕設定外觀;動作本身仍需要相應行為。table table-striped table-hover 為語義表格加上樣式,list-group 與 list-group-item 為清單加上樣式,form-control 則設定表單控制項外觀。樣式不會自動提供標籤或驗證規則。table-dark 這類類別應放在預期的表格區域,表頭儲存格仍使用 th。
部分互動元件,例如摺疊、模態視窗及下拉選單,需要 Bootstrap JavaScript。本地 bundle 可以提供所需外掛及 Popper 支援。靜態排版使用欄位、邊框或按鈕樣式時,不需要因此加入腳本。響應式摺疊導覽列除了 navbar-expand-lg,還需要配對的摺疊標記與行為。
Bootstrap Icons 是獨立的圖示集合。字體圖示類別需要本地圖示樣式表與字體檔案,也可以改用 SVG。圖示若只裝飾旁邊已有的可見文字,可以使用 aria-hidden="true";只有圖示的控制項,則需要描述動作的無障礙名稱。框架可以不同,選擇器、層疊與盒模型仍是共同基礎。
動態效果描述隨時間改變的值
變形改變元素繪畫的方式,卻不重新分配其一般文件流位置。translate、rotate、scale 與 skew 表達不同變形。過渡效果在可動畫化屬性的值改變時,計算中間狀態;關鍵影格動畫則指定一段時間內的一系列值。
.session-link {
display: inline-block;
transition: transform 180ms ease;
}
.session-link:hover,
.session-link:focus-visible {
transform: translateY(-2px);
}
.session-indicator { animation: breathe 2s ease-in-out 2; }
@keyframes breathe {
0%, 100% { opacity: 0.65; }
50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.session-link { transition: none; }
.session-indicator { animation: none; }
}
過渡規則明確指定要改變的屬性,也支援鍵盤焦點狀態。關鍵影格動畫每次持續兩秒,重複兩次。減少動態效果規則移除動畫,但內容與控制項標籤仍然存在。因為原有排版空間沒有改變,變形後的盒子可能與旁邊內容重疊。
練習
思考檢查
練習 1:解釋勝出的宣告
一個帶有 note 類別的段落,位於 reading-card 類別元素之內。樣式表先寫 .reading-card p { color: teal; },再寫 .note { color: purple; }。兩者都是一般、未分層作者規則。哪個顏色勝出?為何把第二條移到較後的 style 元素,也不改變推理?
解答 · 練習 1 解答
藍綠色勝出。.reading-card p 的特異性是 (0,1,1),.note 則是 (0,1,0)。在層疊的這個比較階段,原始碼順序只用來處理平手。移到較後的 style 元素,只改變順序,不改變特異性或來源,因此在題目條件下仍會落敗。
思考檢查
練習 2:計算邊框盒
盒子設有 width: 300px,左右內邊距各 20px、左右邊框各 3px、左右外邊距各 10px。計算採用 content-box 時的邊框盒寬度與水平總範圍,再計算採用 border-box 時的內容寬度。
解答 · 練習 2 解答
採用 content-box 時,邊框盒是 CSS 像素,水平總範圍是 CSS 像素。採用 border-box 時,邊框盒就是宣告的 CSS 像素,內容寬度是 CSS 像素。外邊距仍在該寬度以外。
思考檢查
練習 3:預測響應式欄位
三個同級欄位都帶有 col-12 col-md-6 col-xl-4。使用 Bootstrap 預設網格,解釋視窗寬度為 600、800 及 1250 CSS 像素時的排列。
解答 · 練習 3 解答
600 像素尚未達到 md,所以每欄佔十二單位,三欄上下排列。800 像素適用 md:每欄六單位,首列容納兩欄,第三欄換行。1250 像素適用 xl:每欄四單位,三欄可同列。斷點名稱針對視窗寬度,而不是個別欄位的寬度。
思考檢查
練習 4:找出覆蓋標籤的位置
活動卡片內有絕對定位標籤,設定為 top: 0.5rem; right: 0.5rem。解釋卡片上的 position: relative 有甚麼作用,以及為何文字可能需要在標籤附近加入內邊距。
解答 · 練習 4 解答
本例中的相對定位卡片建立標籤的包含區塊,因此偏移值以該盒子為基準。絕對定位標籤脫離一般文件流,不會在文字旁邊預留位置;合適的內容間距可避免重疊。若沒有該包含區塊,則可能改用另一祖先或初始包含區塊,實際結果取決於文件。
思考檢查
練習 5:區分過渡效果與動畫
在動態效果例子中,甚麼觸發連結的過渡?指示元素的動畫總共持續多少秒?減少動態效果媒體查詢成立時,又會改變甚麼?
解答 · 練習 5 解答
連結進入或離開配對的懸停或可見焦點狀態時,transform 的值改變,從而觸發過渡。指示元素重複兩次,每次兩秒,合共四秒。減少動態效果規則停用連結過渡及指示元素動畫,但連結的狀態樣式與文件內容仍然存在。
本單元的排版規則,作用於語義化 HTML 文件所建立的文件結構。