當選擇必須讓控制項、文字和行為保持一致時,請更新元件狀態。只變更呈現方式時,請覆寫 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() 時會覆蓋你的實驗值。若要讓整個元件保持一致,請透過控制項或更新函式變更尺寸。
為什麼覆寫看起來沒有作用?
先追查值的傳遞路徑,再提高選擇器特異性。宣告本身可能有效,卻在層疊中落敗;也可能是目標自訂屬性已不再被勝出的宣告使用。
- 檢查目標。 確認你選取的是顯示形狀的元素。
- 檢查輸入。 在本例中,尺寸必須是介於 40 到 160 之間的整數。無效更新會在狀態變更前擲出錯誤。
- 檢查使用該值的宣告。 圖塊尺寸使用
var(--tile-size)。勝出的直接width宣告可以讓寬度略過該值,而高度仍使用它。 - 檢查優先順序。 比較適用的宣告、重要性、層級和特異性。
- 檢查後續互動。 如果移動控制項後變更消失,請檢查
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 輸入呈現這些選項,並將裝飾留在樣式表中。每次變更後,請測試控制項互動和重設。預覽、控制項和說明應持續呈現相同的選取值。
資料來源
- CSS 層疊變數模組第 1 級:CSS 自訂屬性 — W3C CSS 工作小組
- HTML 標準:style 元素 — WHATWG
- HTMLInputElement:checked 屬性 — MDN Web Docs
- CSSStyleDeclaration:setProperty() 方法 — MDN Web Docs
- 啟用 Web Inspector — WebKit
- CSSStyleDeclaration:removeProperty() 方法 — MDN Web Docs
- CSS 層疊簡介 — MDN Web Docs
- HTMLElement:style 屬性 — MDN Web Docs
- Window:getComputedStyle() 方法 — MDN Web Docs
- 自訂屬性(--*):CSS 變數 — MDN Web Docs
- 使用 CSS 自訂屬性(變數) — MDN Web Docs