当某个选择必须让控件、文本和行为保持一致时,请更新组件状态。若只更改呈现方式,则覆盖 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 标准中的样式和脚本章节。
<!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 的内边距。在此程序中,这两项更改都不需要新增状态字段,因为它们都不代表已选的选项。
如需进行临时实验,可通过 Safari → Settings… → Advanced → Show features for web developers 启用 Safari 检查器。打开 Develop → Show Web Inspector,或按下 Option-Command-I。这些是在 Mac 上启用检查器的步骤,见 WebKit 的检查器指南。
打开示例后,在控制台运行以下完整代码片段。它会临时更改平铺的颜色,并提供一个用于移除该覆盖的函数:
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
});
刚打开未经修改的示例时,预期值是:内联尺寸标记为 80px,内联宽度为空,解析后的尺寸为 80px。这表明内联自定义属性可以为样式表声明提供值,而不会创建内联的 width 声明。
浏览器和资源方面有哪些限制?
MDN 标注,所有主流浏览器自 2017 年 4 月起都广泛支持 CSS 自定义属性。但这并不能证明所有较旧的浏览器也支持它们。示例在 var() 声明之前包含常规尺寸声明,因此当浏览器不支持自定义属性时,平铺仍会呈现静态外观。参见 MDN 的自定义属性参考。
var(--tile-size, 80px) 中的回退值不能让无法解析自定义属性的浏览器获得兼容性。它也不会验证传入的每个标记:像 blue 这样的已定义值不会仅仅因为存在回退值就变成有效宽度。参见 MDN 的自定义属性指南了解这些限制。
如果较旧的目标浏览器不支持自定义属性,受支持的替代方案是修改渲染器,让它使用 setProperty() 写入常规内联的 width、height 和 border-radius 声明。保留相同的状态验证和控件同步方式。这会改变渲染约定,因此应移除互相冲突的渲染器写入,并在目标浏览器中进行验证,不要假设支持情况完全相同。
对于这个本地预览,实际做法很明确:将形状和尺寸选择放在状态中,通过少量 CSS 输入渲染这些选择,并将装饰样式保留在样式表中。每次更改后,都要测试一次控件交互和重置操作。预览、控件和说明应始终展示相同的所选值。
来源
- CSS Custom Properties for Cascading Variables Module Level 1 — W3C CSS 工作组
- HTML Standard: The style element — WHATWG
- HTMLInputElement: checked property — MDN Web Docs
- CSSStyleDeclaration: setProperty() method — MDN Web Docs
- Enabling Web Inspector — WebKit
- CSSStyleDeclaration: removeProperty() method — MDN Web Docs
- Introduction to the CSS cascade — MDN Web Docs
- HTMLElement: style property — MDN Web Docs
- Window: getComputedStyle() method — MDN Web Docs
- Custom properties (--*): CSS variables — MDN Web Docs
- Using CSS custom properties (variables) — MDN Web Docs