CSS 电梯:纯 CSS 实现的状态机与楼层导航

本篇依然来自于我们的 《前端周刊》 项目!

由团队成员 掘金安东尼 翻译,欢迎大家 进群 持续追踪全球最新前端资讯!!

原文:css-tricks.com/css-elevato...

作为一个对状态机痴迷的开发者,我常常会被一些文章点燃灵感,比如那篇《用 HTML 复选框和 CSS 打造完整状态机》。

纯 CSS 驱动的状态机展现出的力量让我十分着迷,我不禁开始想:能不能做一个更简单、更互动、而且完全不用宏的版本?于是,这个想法催生了一个项目------我用 CSS 搭建了一个电梯模拟器,带有方向指示器、过渡动画、计数器,甚至还考虑了无障碍功能。

在这篇文章里,我会带你走一遍完整过程:我如何用现代 CSS 特性------比如自定义属性、计数器、:has() 伪类和 @property------构建一个完全交互的电梯。它能知道自己在哪一层,要去哪里,以及需要多久才能到达。完全不需要 JavaScript。在线体验:codepen.io


用 CSS 变量定义状态

电梯系统的核心在于 CSS 自定义属性来追踪状态。我用几条 @property 规则来支持类型化值和动画过渡:

less 复制代码
@property --current-floor {
  syntax: "<integer>";
  initial-value: 1;
  inherits: true;
}

@property --previous {
  syntax: "<number>";
  initial-value: 1;
  inherits: true;
}

@property --relative-speed {
  syntax: "<number>";
  initial-value: 4;
  inherits: true;
}

@property --direction {
  syntax: "<integer>";
  initial-value: 0;
  inherits: true;
}

这些变量能让我比较电梯的当前楼层和上一层,计算移动速度,并驱动相应的动画与过渡。

普通的 CSS 自定义属性(例如 --current-floor)虽然能在各处传值,但浏览器默认只把它当成字符串:它根本不知道 5 是数字、颜色,还是你猫的名字。既然不知道,就无法做动画。

这时候 @property 就派上用场了。通过"注册"变量,我可以明确告诉浏览器这是 <number><length> 类型,并设定初始值。这样浏览器就能生成平滑的中间帧。不然,我的电梯只能"瞬移"到新楼层------显然,这不是我想要的乘坐体验。


简单 UI:用单选按钮选择楼层

单选按钮就是状态的触发器。每一层对应一个 radio 输入,然后用 :has() 检测哪一个被选中:

python 复制代码
<input type="radio" id="floor1" name="floor" value="1" checked>
<input type="radio" id="floor2" name="floor" value="2">
<input type="radio" id="floor3" name="floor" value="3">
<input type="radio" id="floor4" name="floor" value="4">
css 复制代码
.elevator-system:has(#floor1:checked) {
  --current-floor: 1;
  --previous: var(--current-floor);
}

.elevator-system:has(#floor2:checked) {
  --current-floor: 2;
  --previous: var(--current-floor);
}

这样一来,电梯系统就成了一个状态机,选择不同按钮会触发过渡和计算。


用动态变量模拟运动

要模拟电梯上升/下降,我用 transform: translateY(...),结合 --current-floor 来计算:

css 复制代码
.elevator {
  transform: translateY(calc((1 - var(--current-floor)) * var(--floor-height)));
  transition: transform calc(var(--relative-speed) * 1s);
}

动画时长取决于要跨越的楼层数:

css 复制代码
--abs: calc(abs(var(--current-floor) - var(--previous)));
--relative-speed: calc(1 + var(--abs));

解释一下:

  • --abs 得到要移动的楼层数(绝对值)。
  • --relative-speed 让动画在跨越更多楼层时更慢。

比如从 1 楼到 4 楼,动画就比从 2 楼到 3 楼耗时更久。这完全依靠 CSS calc() 的数学运算完成。


箭头方向的逻辑

电梯的箭头根据楼层变化指向上或下:

css 复制代码
--direction: clamp(-1, calc(var(--current-floor) - var(--previous)), 1);

.arrow {
  scale: calc(var(--direction) * 2);
  opacity: abs(var(--direction));
  transition: all 0.15s ease-in-out;
}
  • clamp() 限制结果在 -1 到 1 之间。
  • 1 表示上行,-1 表示下行,0 表示静止。
  • 这个结果被用于缩放箭头,实现翻转和透明度变化。

轻量的数学逻辑+视觉效果,完全无须脚本。


用延迟模拟"记忆"

CSS 原生不记得"上一状态"。我用延迟更新 --previous 来模拟:

css 复制代码
.elevator-system {
  transition: --previous calc(var(--delay) * 1s);
  --delay: 1;
}

在延迟期间,--previous 落后于 --current-floor,从而能计算方向与速度。延迟结束后,两者同步。这就像给 CSS 加了一点"记忆",替代了 JS 里常见的状态保存。


楼层显示与 Unicode 风格

楼层显示则通过 CSS 计数器来处理:

css 复制代码
#floor-display:before {
  counter-reset: display var(--current-floor);
  content: counter(display, top-display);
}

定义一个自定义计数器样式,使用 Unicode 圆圈数字:

css 复制代码
@counter-style top-display {
  system: cyclic;
  symbols: "\278A" "\2781" "\2782" "\2783";
  suffix: "";
}

比如 \278A\2783 就是 ➊、➋、➌、➍,让电梯面板更有设计感。


用 aria-live 提升无障碍体验

虽然 CSS 改不了 DOM 文本,但可以用 ::before + counter() 来让屏幕阅读器读出当前楼层:

bash 复制代码
<div class="sr-only" aria-live="polite" id="floor-announcer"></div>
css 复制代码
#floor-announcer::before {
  counter-reset: floor var(--current-floor);
  content: "Now on floor " counter(floor);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

视觉上隐藏,但对辅助设备可见,保证体验完整。


实际应用场景

这个电梯不仅是个"玩具",更像是一份蓝图:

  • 无需 JS 的交互原型
  • 表单进度指示器
  • 游戏界面里的状态机制
  • 逻辑谜题或教育工具(纯 CSS 状态追踪)
  • 在性能或安全限制环境(如邮件、部分 CMS 组件)减少 JS 依赖

总结

一个小实验,最终演化成了完整的 CSS 状态机:能动、能指示方向、还能"报楼层"。这说明现代 CSS 的能力远超我们想象。用 :has()@property、计数器和一点数学,你就能构建响应式、美观,甚至无障碍友好的系统------完全不用写 JavaScript。

相关推荐
橙子家15 分钟前
浏览器缓存之【结构化数据库与缓存】: IndexedDB、Cache storage 和 Storage buckets
前端
user205855615181320 分钟前
X6 中边悬浮置顶,规避 `mouseleave` 事件丢失问题
前端
李明卫杭州21 分钟前
CSS aspect-ratio 属性完全指南
前端
Pedantic2 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘3 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆3 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师4 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆4 小时前
VSCode自动格式化三要素
前端
爱勇宝5 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员