CSS Elevator: 순수 CSS 상태 머신 엘리베이터 구현
1. 개요
순수 CSS 만으로 엘리베이터(층 이동) 시뮬레이션을 만들며, 상태 추적·애니메이션·방향 표시·접근성(aria-live)까지 구현한 사례이다. JavaScript 없이 @property
, CSS 커스텀 프로퍼티, :has()
, 카운터(counter), 수학적 calc()
표현식, 지연(transition) 기법을 조합해 현재 층 / 이전 층 / 이동 속도 / 방향을 파생값으로 계산한다.
2. 핵심 아이디어 및 사용 기술
-
상태 값 전파:
--current-floor
,--previous
,--relative-speed
,--direction
등 커스텀 프로퍼티. -
타이핑 & 애니메이션 가능화:
@property
로 숫자형 등록해 중간 프레임 보간. -
UI → 상태 연결: 라디오 버튼 + 상위 컨테이너
:has(#floorX:checked)
선택자. -
이동 연산: 층 차이 절댓값을 이용해 이동 시간 비례 증가.
-
방향 판정:
clamp(-1, delta, 1)
→ -1(하행)/0(정지)/1(상행). -
메모리 시뮬레이션:
--previous
갱신 지연(transition 대상)으로 “이전 값” 유지. -
표시 & 접근성: CSS 카운터 +
@counter-style
+aria-live
숨김 영역.
3. 상태 정의 (@property 및 커스텀 프로퍼티)
@property
등록으로 숫자 해석 + 애니메이션을 가능하게 하여 “층 전환 시 순간 이동(snap)” 문제를 해결한다.
@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;
}
4. 층 선택 UI (라디오 버튼 + :has())
라디오 버튼을 통한 단일 선택 상태를 상위 .elevator-system
이 :has()
로 감지하여 관련 변수를 설정한다.
<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">
.elevator-system:has(#floor1:checked) {
--current-floor: 1;
--previous: var(--current-floor);
}
.elevator-system:has(#floor2:checked) {
--current-floor: 2;
--previous: var(--current-floor);
}
5. 이동 계산 (변위·속도)
엘리베이터 이동은 Y 변환(translateY) 로 구현하며, 이동 시간은 층 수 차이에 비례해 길어진다.
.elevator {
transform: translateY(calc((1 - var(--current-floor)) * var(--floor-height)));
transition: transform calc(var(--relative-speed) * 1s);
}
/* 파생 값 (개념상) */
--abs: calc(abs(var(--current-floor) - var(--previous)));
--relative-speed: calc(1 + var(--abs));
핵심 포인트: 더 많은 층을 이동할수록 --abs
증가 → --relative-speed
증가 → 애니메이션 시간 연장.
6. 방향 판단과 화살표
층 차이를 -1, 0, 1 범위로 압축(clamp)하여 방향과 가시성을 제어한다.
--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;
}
의미: 1 상행 / -1 하행 / 0 정지 → scale(뒤집힘) & opacity(표시/숨김) 제어.
7. 지연 기반 ‘메모리’ (이전 상태 유지)
CSS 는 과거 값을 저장하지 못하므로 --previous
업데이트 자체를 transition 지연에 태워 “늦게 따라오게” 한다.
.elevator-system {
transition: --previous calc(var(--delay) * 1s);
--delay: 1;
}
지연 동안 --previous
가 과거 층을 유지 → 현재와 차이 계산 가능 → 방향·속도 산출 후 동기화.
8. 층 표시 (카운터 + 유니코드 스타일)
카운터를 이용해 층 번호를 심미적으로 표현한다.
#floor-display:before {
counter-reset: display var(--current-floor);
content: counter(display, top-display);
}
@counter-style top-display {
system: cyclic;
symbols: "\278A" "\2781" "\2782" "\2783";
suffix: "";
}
유니코드 ➊ ➋ ➌ ➃ 형태로 시각적 차별성 부여.
9. 접근성 (aria-live 및 시각 숨김)
aria-live="polite"
영역에 카운터 기반 텍스트를 CSS ::before
로 삽입해 층 변화를 스크린리더에 공지한다.
<div class="sr-only" aria-live="polite" id="floor-announcer"></div>
#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;
}
10. 실용적 활용 사례
본문에서 제시한 응용 가능성:
-
인터랙티브 프로토타입 (JavaScript 없이)
-
폼 진행 상황(progress) 표시
-
게임 UI (인벤토리·상태 메커닉)
-
퍼즐·교육 도구 (CSS 상태 추적)
-
스크립트 제한 환경(정적 앱, 일부 CMS, 이메일 등)에서 JS 의존도 감소
11. 실용적인 팁과 주의사항
※ 본문 내용만 재구성
-
@property 사용은 필수적: 미등록 시 숫자 값은 문자열 취급되어 **애니메이션이 ‘즉시 점프’**한다.
-
지연 기반 메모리 트릭: 실제 상태 저장은 아니며, 단지 transition 시점 차이를 이용한 의사(previous) 상태 유지.
-
수학 표현 활용:
abs()
,clamp()
조합으로 추가 로직 없이 방향·속도·표시를 단순화. -
접근성 고려: 가시적 텍스트 변경 없이도
aria-live
+ 카운터로 층 안내 음성 출력. -
과도한 확장 시 고려(본문 언급 범위 내 개념): 더 많은 층 추가 시 동일 패턴을 라디오 입력·조건 블록으로 확대 가능.
12. 코드 스니펫 모음 (본문 발췌)
이미 위 섹션별로 포함: @property
정의, 라디오 입력, 상태 선택자, 이동·방향 계산, 카운터, 접근성 스타일.
13. 학습 리소스 및 참고 자료
- 언급된 참고 글 제목: “A Complete State Machine Made with HTML Checkboxes and CSS” (제목만 본문에 존재)
14. 핵심 요약 포인트 (재정리)
-
순수 CSS 상태 머신: 입력(Radio) + 선택자(
:has()
)로 상태 결정. -
정량 속성 등록(@property) 로 숫자 애니메이션 가능.
-
층 이동 로직: 층 차이 → 시간/방향/이동거리 파생.
-
메모리 지연 기법: transition 대상 커스텀 프로퍼티로 이전 상태 의사 유지.
-
표시 + 접근성: 카운터 + 유니코드 + aria-live.