CSS 엘리베이터: 순수 CSS 상태 기계와 층 이동 기능 | CSS-Tricks


CSS Elevator: 순수 CSS 상태 머신 엘리베이터 구현

1. 개요

순수 CSS 만으로 엘리베이터(층 이동) 시뮬레이션을 만들며, 상태 추적·애니메이션·방향 표시·접근성(aria-live)까지 구현한 사례이다. JavaScript 없이 @property, CSS 커스텀 프로퍼티, :has(), 카운터(counter), 수학적 calc() 표현식, 지연(transition) 기법을 조합해 현재 층 / 이전 층 / 이동 속도 / 방향을 파생값으로 계산한다.

2. 핵심 아이디어 및 사용 기술

  1. 상태 값 전파: --current-floor, --previous, --relative-speed, --direction 등 커스텀 프로퍼티.

  2. 타이핑 & 애니메이션 가능화: @property로 숫자형 등록해 중간 프레임 보간.

  3. UI → 상태 연결: 라디오 버튼 + 상위 컨테이너 :has(#floorX:checked) 선택자.

  4. 이동 연산: 층 차이 절댓값을 이용해 이동 시간 비례 증가.

  5. 방향 판정: clamp(-1, delta, 1) → -1(하행)/0(정지)/1(상행).

  6. 메모리 시뮬레이션: --previous 갱신 지연(transition 대상)으로 “이전 값” 유지.

  7. 표시 & 접근성: 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. 실용적 활용 사례

본문에서 제시한 응용 가능성:

  1. 인터랙티브 프로토타입 (JavaScript 없이)

  2. 폼 진행 상황(progress) 표시

  3. 게임 UI (인벤토리·상태 메커닉)

  4. 퍼즐·교육 도구 (CSS 상태 추적)

  5. 스크립트 제한 환경(정적 앱, 일부 CMS, 이메일 등)에서 JS 의존도 감소

11. 실용적인 팁과 주의사항

※ 본문 내용만 재구성

  • @property 사용은 필수적: 미등록 시 숫자 값은 문자열 취급되어 **애니메이션이 ‘즉시 점프’**한다.

  • 지연 기반 메모리 트릭: 실제 상태 저장은 아니며, 단지 transition 시점 차이를 이용한 의사(previous) 상태 유지.

  • 수학 표현 활용: abs(), clamp() 조합으로 추가 로직 없이 방향·속도·표시를 단순화.

  • 접근성 고려: 가시적 텍스트 변경 없이도 aria-live + 카운터로 층 안내 음성 출력.

  • 과도한 확장 시 고려(본문 언급 범위 내 개념): 더 많은 층 추가 시 동일 패턴을 라디오 입력·조건 블록으로 확대 가능.

12. 코드 스니펫 모음 (본문 발췌)

이미 위 섹션별로 포함: @property 정의, 라디오 입력, 상태 선택자, 이동·방향 계산, 카운터, 접근성 스타일.

13. 학습 리소스 및 참고 자료

  • 언급된 참고 글 제목: “A Complete State Machine Made with HTML Checkboxes and CSS” (제목만 본문에 존재)

14. 핵심 요약 포인트 (재정리)

  1. 순수 CSS 상태 머신: 입력(Radio) + 선택자(:has())로 상태 결정.

  2. 정량 속성 등록(@property) 로 숫자 애니메이션 가능.

  3. 층 이동 로직: 층 차이 → 시간/방향/이동거리 파생.

  4. 메모리 지연 기법: transition 대상 커스텀 프로퍼티로 이전 상태 의사 유지.

  5. 표시 + 접근성: 카운터 + 유니코드 + aria-live.

1개의 좋아요