重新思考 UI 状态:CSS Range Syntax vs Class Toggling
Source: Dev.to
传统模式:JavaScript 控制视觉状态
想象一个日历,用户可以选择开始日期和结束日期。典型的实现方式如下:
days.forEach(day => {
const value = Number(day.dataset.day);
if (value >= start && value 12
现在 CSS 使用新兴的 Range Syntax 来进行比较:
.day-now {
background-color: if(
style(--day-start <= --day <= --day-end): #8b0000;
else: rgba(255, 255, 255, 0.05);
);
}
没有循环,没有类切换,也没有 DOM 变更。JavaScript 更新状态;CSS 负责呈现。
实时演示(CSS Range Syntax)
在此版本中,JavaScript 只更新 --day-start 和 --day-end。CSS 直接评估范围条件。需要 Chrome 142+(实验性支持)。尝试在 JS 面板中更改 start 和 end 的值,观察 UI 的响应。
为什么这在架构上有趣
JavaScript
- 处理交互
- 更新状态值
CSS
- 评估视觉条件
- 基于状态进行渲染
决定 外观如何 的逻辑位于它应在的地方——CSS。
DOM 重构变得不那么脆弱
在传统做法中,JS 可能依赖如下选择器:
document.querySelectorAll('.calendar .row .day');
重构 DOM 可能会破坏这段逻辑。而在 CSS 驱动的模型中,JS 并不关心结构;它只设置 --day-start 和 --day-end。只要每个日期暴露 --day,样式就能正常工作,从而降低了结构耦合。
浏览器支持 — 以及实用的回退方案
Range Syntax 仍在发展中,尚未在稳定浏览器中得到完整支持。不过,这一架构模式本身并不依赖它。今天我们可以使用 clamp() 和自定义属性的算术运算来模拟类似的逻辑:
.day-now {
--gte-start: clamp(0, calc(var(--day) - var(--day-start) + 1), 1);
--lte-end: clamp(0, calc(var(--day-end) - var(--day) + 1), 1);
--in-range: calc(var(--gte-start) * var(--lte-end));
background-image: linear-gradient(
rgba(139, 0, 0, var(--in-range)),
rgba(139, 0, 0, var(--in-range))
);
}
发生了什么
- 小于零的值会被 clamp 为
0,大于一的值会被 clamp 为1。 - 乘法模拟了逻辑 AND。
- 结果控制不透明度。
这个回退方案比 Range Syntax 更冗长,但在今天是完全可行的。Range Syntax 主要提升了代码的可读性。
实时演示(Clamp 回退)
相同的架构模式——今天使用 clamp() 实现。JavaScript 仍然只更新状态值。
权衡与约束
该模式并非在所有情况下都更好。需要考虑:
- 可读性 — 某些团队可能觉得类切换比 CSS 算术更直观。
- 调试 — 在 CSS 中编写条件逻辑可能对习惯了命令式控制流的开发者来说不太熟悉。
- 大规模状态 — 如果 UI 状态变得深度相互依赖或复杂,JavaScript 仍可能是更好的协调层。
关键不是“用 CSS 替代 JS”。而是重新定义每一层负责的职责。
更广阔的方向
自定义属性、容器查询、:has()、滚动驱动动画、进阶的 attr() —— CSS 正在不断获得表达能力。Range Syntax 正是这一路径的一部分。它并不会消除 JavaScript,但可以减少我们在视觉状态管理上对它的依赖。这值得我们重新思考。