當選擇必須讓控制項、文字和行為保持一致時,請更新元件狀態。只變更呈現方式時,請覆寫 CSS。本指南以 Mac 上 Safari 中的本機圖塊預覽為例,說明如何選擇並驗證這兩種做法。

讀者的任務很明確:修改自己編寫的小型網頁,讓形狀控制項保持同步,同時讓裝飾樣式易於編輯。這裡的元件狀態是指由網頁自己的更新函式管理的 JavaScript 物件。本例不假設使用任何框架或特定產品的編輯功能。

哪些變更該放進元件狀態?

先問問自己,變更後哪些項目必須彼此一致。如果選取圓形圖塊必須同步更新核取方塊、預覽和文字說明,就讓狀態成為這項選擇的唯一來源。接著從中推導出這三種呈現結果。這是一項實作建議:它能明確表達選擇與顯示結果之間的關係。

CSS 自訂屬性(例如 --tile-size)用途不同。它會將值提供給透過 var() 引用它的宣告。自訂屬性會參與層疊,通常也會繼承;即使由 JavaScript 設定,其行為仍遵循 CSS 規則。CSS 自訂屬性規範定義了這項規則。

編輯前可先參考以下判斷方式:

要求的變更 建議的管理位置 本例中的原因
讀者選擇圓形或方形圖塊 元件狀態 核取方塊、說明和形狀必須一致
讀者選擇圖塊大小 元件狀態,並輸出到 CSS 範圍控制項、說明和尺寸共用同一個值
每個圖塊都使用不同的裝飾色彩 樣式表宣告 不需要更新選取值或文字
預覽周圍需要更多空間 樣式表宣告 這會改變圖塊周圍的版面配置
檢查時需要暫時調整尺寸 本機 CSS 覆寫 這只是視覺實驗,之後可以移除

具體歸屬取決於任務。在本預覽中,色彩屬於 CSS,因為它是固定裝飾。如果之後新增色彩選擇器和所選色彩的說明,就應將這項選擇視為狀態,並據此呈現視覺效果。避免為同一個選擇建立兩個可獨立編輯的值。

原生控制項也有自己的狀態。對核取方塊而言,請讀取目前的 checked 屬性。HTML checked 屬性指定核取狀態的預設值,不會追蹤後續的使用者變更。MDN 的核取方塊屬性參考文件記載了這項差異。

在 Mac 上建立單一自足的預覽

使用純文字程式碼編輯器,將以下完整文件儲存為 tile-preview.html,然後在 Safari 中開啟。保留 .html 副檔名。範例包含自己的 CSS 和 JavaScript,使用系統字型,並以基本幾何圖形繪製圖塊,不依賴外部資源。

樣式表位於文件的 head 中。HTML 將 style 元素定義為嵌入 CSS 的方式,並區分自足的內部樣式與會引用其他資源的樣式表。讓本例保持自足,能明確呈現其樣式輸入。HTML 標準中的 style 與 scripting 章節提供更多資訊。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Tile preview</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      margin: 2rem;
      color: #222;
      background: #fff;
    }
    label { display: block; margin-block: 1rem; }
    .preview { padding: 24px; background: #eee; }
    .tile {
      width: 80px;
      height: 80px;
      border-radius: 0;
      width: var(--tile-size, 80px);
      height: var(--tile-size, 80px);
      border-radius: var(--tile-radius, 0);
      background-color: #547899;
    }
  </style>
</head>
<body>
  <h1>Tile preview</h1>
  <label>
    <input id="round" type="checkbox"> Round tile
  </label>
  <label>
    Tile size
    <input id="size" type="range" min="40" max="160"
           step="1" value="80">
  </label>
  <button id="reset" type="button">Reset choices</button>
  <p id="summary"></p>
  <div class="preview">
    <div id="tile" class="tile" aria-hidden="true"></div>
  </div>
  <script>
    const state = { rounded: false, size: 80 };
    const round = document.getElementById('round');
    const size = document.getElementById('size');
    const summary = document.getElementById('summary');
    const tile = document.getElementById('tile');
    const reset = document.getElementById('reset');

    function render() {
      round.checked = state.rounded;
      size.value = String(state.size);
      tile.style.setProperty('--tile-size', `${state.size}px`);
      tile.style.setProperty('--tile-radius', state.rounded ? '50%' : '0');
      summary.textContent = `Tile A: ${state.rounded ? 'round' : 'square'}, ${state.size}px.`;
    }

    function update(patch) {
      const next = { ...state, ...patch };
      if (typeof next.rounded !== 'boolean' ||
          !Number.isInteger(next.size) ||
          next.size < 40 || next.size > 160) {
        throw new RangeError('Use a boolean and a size from 40 to 160.');
      }
      Object.assign(state, next);
      render();
    }

    round.addEventListener('change', () => {
      update({ rounded: round.checked });
    });
    size.addEventListener('input', () => {
      update({ size: Number(size.value) });
    });
    reset.addEventListener('click', () => {
      update({ rounded: false, size: 80 });
    });
    render();
  </script>
</body>
</html>

此處使用的樣式介面有文件記載:element.style.setProperty() 會寫入行內宣告,包括自訂屬性。屬性名稱使用 CSS 拼寫方式,例如 --tile-size。請參閱 MDN 的 setProperty 參考文件。

試著選取 Round tile、將 Tile size 移到其他值,再選擇 Reset choices。前兩個操作都應更新形狀和說明;重設則應恢復成邊長 80 像素的正方形。在這個程式中,單獨指派 state 的欄位不會呼叫 render()。請透過 update() 處理變更,讓驗證與呈現一併執行。

我們在 Node 中以模擬控制項和樣式物件,檢查了更新與呈現邏輯,包括初始值、變更後的值、無效尺寸和重設。這些檢查都已通過,但無法證明瀏覽器中的呈現效果。請使用上述可見操作,在 Safari 中驗證檔案。

何時該覆寫 CSS 宣告?

若要長期變更裝飾,請編輯已儲存的樣式表。例如,將 .tile 的 background-color 改成其他 CSS 色彩,或調整 .preview 的 padding。在這個程式中,這兩項變更都不需要新增狀態欄位,因為它們都不代表使用者選取的選項。

若要進行暫時實驗,可透過 Safari → Settings… → Advanced → Show features for web developers 啟用 Safari 檢閱器。接著開啟 Develop → Show Web Inspector,或按下 Option-Command-I。這些是在 WebKit 檢閱器指南中記載的 Mac 操作步驟。

開啟範例後,在主控台執行以下完整程式碼片段,暫時變更圖塊色彩,並取得一個能移除此覆寫的函式:

const previewTile = document.getElementById('tile');
if (!previewTile) throw new Error('Open tile-preview.html first.');
previewTile.style.setProperty('background-color', '#8069a6');
function restoreTileColor() {
  previewTile.style.removeProperty('background-color');
}
// Run restoreTileColor() when finished comparing colors.

移除此行內宣告後,其他層疊規則就會再次決定色彩。這不會刪除樣式表中的色彩宣告。MDN 的 removeProperty 參考文件說明了移除方法。若要在本例中保留選定的色彩,讓它在重新載入後仍然有效,請編輯並儲存 CSS 原始碼。

請避免暫時覆寫呈現器管理的值。如果手動變更 --tile-size,說明仍會顯示狀態值,而下次呼叫 render() 時會覆蓋你的實驗值。若要讓整個元件保持一致,請透過控制項或更新函式變更尺寸。

為什麼覆寫看起來沒有作用?

先追查值的傳遞路徑,再提高選擇器特異性。宣告本身可能有效,卻在層疊中落敗;也可能是目標自訂屬性已不再被勝出的宣告使用。

  1. 檢查目標。 確認你選取的是顯示形狀的元素。
  2. 檢查輸入。 在本例中,尺寸必須是介於 40 到 160 之間的整數。無效更新會在狀態變更前擲出錯誤。
  3. 檢查使用該值的宣告。 圖塊尺寸使用 var(--tile-size)。勝出的直接 width 宣告可以讓寬度略過該值,而高度仍使用它。
  4. 檢查優先順序。 比較適用的宣告、重要性、層級和特異性。
  5. 檢查後續互動。 如果移動控制項後變更消失,請檢查 render() 寫入了什麼。

一般行內宣告的優先順序高於一般作者樣式表規則。不過,重要性、動畫和轉場會影響更廣泛的順序。較後面的宣告不一定會勝出,特異性較高的選擇器也無法解決所有衝突。MDN 的層疊指南說明了這些情況。

也要區分行內值與解析後的值。element.style 會公開行內樣式,因此行內值為空,不代表元素沒有樣式。getComputedStyle() 會讀取套用有效樣式表後解析出的值。請參閱 MDN 的 style 參考文件和 getComputedStyle 參考文件。

在範例頁面的主控台中執行以下完整診斷程式碼片段:

const inspectedTile = document.getElementById('tile');
if (!inspectedTile) throw new Error('Open tile-preview.html first.');
console.table({
  inlineSizeToken: inspectedTile.style.getPropertyValue('--tile-size'),
  inlineWidth: inspectedTile.style.getPropertyValue('width'),
  resolvedWidth: getComputedStyle(inspectedTile).width,
  resolvedHeight: getComputedStyle(inspectedTile).height
});

剛開啟未修改的範例後,預期值是:行內尺寸 token 為 80px、行內 width 為空,而解析後的尺寸為 80px。這說明行內自訂屬性可以為樣式表宣告提供值,而不會建立行內的 width 宣告。

哪些瀏覽器與資源限制需要留意?

MDN 標示 CSS 自訂屬性自 2017 年 4 月起已廣泛獲得瀏覽器支援。這並不代表所有較舊的瀏覽器都支援。本例在 var() 宣告前加入一般尺寸宣告,因此若瀏覽器不支援自訂屬性,圖塊仍會維持靜態外觀。請參閱 MDN 的自訂屬性參考文件。

var(--tile-size, 80px) 中的 fallback 無法讓不支援自訂屬性的瀏覽器取得相容性,也不會驗證每個提供的 token:即使有 fallback,像 blue 這樣已定義的值仍無法成為有效寬度。MDN 的自訂屬性指南說明了這些限制。

如果目標是缺少自訂屬性支援的舊版瀏覽器,支援的替代做法是修改呈現器,讓它透過 setProperty() 寫入一般行內 width、height 和 border-radius 宣告。保留相同的狀態驗證與控制項同步機制。這會改變呈現契約,因此請移除相互衝突的呈現器寫入,並在目標瀏覽器中驗證,不要假設各瀏覽器支援程度相同。

在這個本機預覽中,實際做法很明確:使用狀態管理形狀與尺寸選擇,透過少量 CSS 輸入呈現這些選項,並將裝飾留在樣式表中。每次變更後,請測試控制項互動和重設。預覽、控制項和說明應持續呈現相同的選取值。

資料來源