明確設定 HTML 方向、使用邏輯屬性設定方向性間距,並在版面項目之間使用 gap,讓 LTR 與 RTL 頁面共用同一個 CSS 檔案。本指南會建立一個本機雙頁範例,供你在 Mac 瀏覽器中比較。
這項任務的目標是,讓同一個小型元件在行內方向改變時,仍維持預期的間距。你需要純文字編輯器和目前版本的桌面瀏覽器。此範例使用虛構標籤和裝飾圓形,不使用框架、外部資源或 JavaScript。
共用樣式表應控制哪些項目?
由 HTML 負責方向,樣式表負責間距。兩個頁面都載入相同的 CSS 資源,並使用相同的元件類別;頁面根元素則提供 dir="ltr" 或 dir="rtl"。HTML 將 rel="stylesheet" 定義為一種外部資源連結,可用於文件樣式設定。HTML 標準:樣式表連結。
方向已知時,請使用 HTML dir。後代元素通常會繼承方向,而 lang 不會設定方向。在這個受控比較中,請優先明確指定方向,而非使用 dir="auto":自動方向取決於內容,會讓拉丁字母範例標籤選擇 LTR。MDN:dir 屬性。
關鍵在於區分實體邊緣和內容流中的位置。在此範例的 horizontal-tb 書寫模式中,行內起始位置在 LTR 時是左側,在 RTL 時是右側。區塊起始位置則始終在上方。邏輯屬性可直接表達這些關係。MDN:邏輯邊界、框線與內距。
| 預期的間距 | 共用 CSS 的選擇 | 方向改變時的結果 |
|---|---|---|
| 元件前導邊緣內側的間距 | padding-inline-start |
移至另一個實體側 |
| 兩側行內邊緣內側的間距 | padding-inline |
依新方向套用起始和結束值 |
| Flexbox 項目之間的間距 | gap |
保持指定的項目間距 |
| 此水平版面中的上下間距 | padding-block |
仍位於上方和下方 |
| 行內內容流中元素前方的外距 | margin-inline-start |
對應至新的行內起始側 |
外距和內距的對應方式取決於元素的書寫模式與方向。gap 則會在支援的版面容器中建立間隔。兩者用途不同:使用內距環繞內容,使用 gap 分隔同層項目。MDN:邏輯間距、MDN:gap。
建立共用一個 CSS 檔案的兩個本機頁面
在 Mac 上建立名為 spacing-demo 的資料夾。將下列檔案以純文字儲存,並使用指定的副檔名。避免使用會在 .html 或 .css 後加上 .txt 的編輯器。
spacing-demo/
ltr.html
rtl.html
components.css
將以下完整的 HTML 文件儲存為 ltr.html。接著另存一份為 rtl.html,只將根元素屬性從 dir="ltr" 改成 dir="rtl"。保留相同的樣式表參照和元件標記。標籤維持英文,因為這是虛構的版面比較範例。
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Component spacing sample</title>
<link rel="stylesheet" href="components.css">
</head>
<body>
<main class="demo">
<h1>Spacing sample</h1>
<article class="sample-card">
<span class="sample-card__shape" aria-hidden="true"></span>
<div class="sample-card__copy">
<h2 class="sample-card__title">Module A</h2>
<p class="sample-card__description">A compact sample tile</p>
</div>
<span class="sample-card__tag">Set B</span>
</article>
</main>
</body>
</html>
接著將以下完整樣式表儲存為 components.css。刻意設定不相等的行內內距,讓方向變化容易檢查:較大的縮排應跟隨行內起始位置。
:root {
--space-small: 8px;
--space-medium: 16px;
--space-large: 24px;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 24px;
font-family: system-ui, sans-serif;
line-height: 1.5;
color: #243447;
background: #f2f5f8;
}
.demo {
writing-mode: horizontal-tb;
max-width: 640px;
margin-inline: auto;
}
.sample-card {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--space-medium);
padding-block: var(--space-medium);
padding-inline: var(--space-large) var(--space-small);
border: 1px solid #8594a5;
border-radius: 12px;
background: #ffffff;
}
.sample-card__shape {
flex: 0 0 32px;
width: 32px;
height: 32px;
border-radius: 50%;
background: #5477a3;
}
.sample-card__copy {
flex: 1 1 0;
min-width: 0;
overflow-wrap: anywhere;
}
.sample-card__title,
.sample-card__description {
margin: 0;
}
.sample-card__title {
font-size: 18px;
}
.sample-card__description {
margin-block-start: var(--space-small);
}
.sample-card__tag {
flex: 0 0 auto;
padding-block: 4px;
padding-inline: var(--space-small);
border: 1px solid #8594a5;
border-radius: 6px;
}
此處的 padding-inline: 24px 8px 表示起始內距,接著是結束內距,不代表左側接著右側。只提供一個值時,會同時套用至兩側行內邊緣。MDN:padding-inline。
兩個頁面都保留 flex-direction: row。Flexbox 的列方向本來就會遵循容器方向:LTR 時由左向右,RTL 時由右向左。在 RTL 版本加上 row-reverse 會再次反轉方向。MDN:flex-direction。
中性的圓形沒有方向性意義,因此其外觀維持不變,位置則隨列方向移動。拉丁字母標籤的字母順序也維持不變;此範例檢查的是元件版面,而非翻譯後的文字。
如何確認間距確實共用?
在 Mac 瀏覽器中開啟兩個 HTML 檔案,並以相同的視窗寬度比較。下表列出根據宣告預期的值,並非瀏覽器回報的測量結果。卡片框線與內距是分開計算的。
| 檢查項目 | LTR 頁面 | RTL 頁面 |
|---|---|---|
| 圓形位置 | 左側第一個項目 | 右側第一個項目 |
| 卡片左側內距 | 24px |
8px |
| 卡片右側內距 | 8px |
24px |
| 相鄰 Flexbox 項目之間的間距 | 16px |
16px |
| 卡片上下內距 | 16px |
16px |
| 說明文字前的間距 | 上方 8px |
上方 8px |
將資源共用和方向分開測試。在 components.css 中將 --space-medium 從 16px 改為 20px,儲存後重新載入兩個頁面。由於項目間距和區塊內距的宣告都參照同一個變數,兩者都應變大。比較完成後,還原為 16px。
接著縮窄瀏覽器視窗,並將說明文字換成較長的虛構標籤,例如 Module A extended sample description。檢查文字是否換行,且不會遮住圓形或標籤。這也能進行另一項實用測試:元件在某個寬度下可能正確鏡像,但在另一個寬度下卻變得擁擠。
不要只根據 gap 判斷所有可見間距。項目外距、內部內距和分散對齊都可能讓可見間隔大於宣告的間隔。此範例重設了文字外距,並避免使用分散對齊,讓比較更清楚。MDN:gap 說明。
如何在不產生間距衝突的情況下遷移既有元件?
先釐清每個現有宣告的用途。文字前方的縮排通常應對應至行內起始位置;若設計刻意指定實體邊緣,則仍可使用實體屬性。應依照設計意圖轉換,而非機械式地替換每個 left。
若元件類似此範例,請依照以下順序操作:
- 找出提供方向的祖先元素。
- 將方向性內部間距改為邏輯內距。
- 必要時,將方向性外部間距改為邏輯外距。
- 對 Flexbox 項目的一般間距使用父元素的
gap,並移除原先提供相同間距的外距。 - 在兩種方向下,重新比較不相等的內距和窄視窗呈現。
遷移時,檢查是否有舊的實體屬性宣告和後續簡寫屬性。對應至同一側的邏輯屬性與實體屬性會一同參與級聯;邏輯屬性不會只因為是邏輯屬性就自動優先。在其他級聯條件相同時,後續的 padding 宣告可能會重設先前透過邏輯屬性指定的內距。CSS 邏輯屬性和值第 1 級:盒模型屬性。
也要留意方向的邊界。若巢狀元件自行設定 dir="ltr",即使位於 RTL 頁面中,也應使用 LTR 對應方式。相反地,margin-inline 本身不會被繼承,因此請為每個元件指定所需的間距宣告,不要預期祖先元素的外距會轉移到該元件。MDN:方向繼承、MDN:margin-inline 正式定義。
哪些瀏覽器限制會影響此做法?
MDN 指出,padding-inline 和 margin-inline 自 2021 年 4 月起已廣泛支援於各種瀏覽器。這表示目前的桌面瀏覽器可使用這些屬性,但不代表所有舊版瀏覽器或參考頁面上的每一種較新語法都支援。此範例使用一般長度單位和 auto,不使用較新的尺寸函式。MDN:padding-inline、MDN:margin-inline。
Flexbox gap 的支援歷程不同。Safari 從 14.1 版開始支援;Grid gap 則早在 Safari 12 就已支援。WebKit 特別指出,屬性功能查詢無法區分這兩種情境,因此在舊版 Safari 中,單靠 @supports (gap: 16px) 不足以偵測 Flexbox gap 支援情況。WebKit:Safari 14.1 的新 WebKit 功能。
如果目標瀏覽器支援邏輯外距,卻不支援 Flexbox gap,請將以下替代片段附加在主要樣式表規則之後。它能為此特定單列元件提供間距。請依照範例使用此片段取代目前的 gap 間距;它並不適用於換行列的一般情況。
/* Optional single-row replacement for Flexbox gap. */
.sample-card {
gap: 0;
}
.sample-card > * + * {
margin-inline-start: var(--space-medium);
}
若瀏覽器也不支援邏輯屬性,此備援方式便不夠用。請使用目前版本的瀏覽器完成本機任務,或為舊版目標瀏覽器撰寫並測試明確的 LTR 和 RTL 實體屬性規則。請將這項瀏覽器特定的決策,與共用樣式表的主要設計分開處理。
完成後的比較應呈現一致的原則:HTML 提供方向,共用 CSS 提供間距,而元件的前導內縮跟隨行內起始位置。每當變更元件的間距或版面時,保留這兩個本機頁面作為精簡的迴歸測試範例。
來源
- HTML 標準 — 連結類型「stylesheet」 — WHATWG。
- dir HTML 全域屬性 — MDN Web Docs。
- 外距、框線與內距的邏輯屬性 — MDN Web Docs。
- padding-inline CSS 屬性 — MDN Web Docs。
- margin-inline CSS 屬性 — MDN Web Docs。
- gap CSS 屬性 — MDN Web Docs。
- flex-direction CSS 屬性 — MDN Web Docs。
- CSS 邏輯屬性和值模組第 1 級 — W3C。
- Safari 14.1 的新 WebKit 功能 — WebKit。