明確設定 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。

若元件類似此範例,請依照以下順序操作:

  1. 找出提供方向的祖先元素。
  2. 將方向性內部間距改為邏輯內距。
  3. 必要時,將方向性外部間距改為邏輯外距。
  4. 對 Flexbox 項目的一般間距使用父元素的 gap,並移除原先提供相同間距的外距。
  5. 在兩種方向下,重新比較不相等的內距和窄視窗呈現。

遷移時,檢查是否有舊的實體屬性宣告和後續簡寫屬性。對應至同一側的邏輯屬性與實體屬性會一同參與級聯;邏輯屬性不會只因為是邏輯屬性就自動優先。在其他級聯條件相同時,後續的 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 提供間距,而元件的前導內縮跟隨行內起始位置。每當變更元件的間距或版面時,保留這兩個本機頁面作為精簡的迴歸測試範例。

來源