이 글에서 다루는 것
홈 페이지 히어로 옆 라이브 노트 데모와 다크 파이프라인 스크롤 연출을, 애니메이션 라이브러리 없이 CSS만으로 맞춘 하루다. 병행해서 폰트 CLS 정리와 활성 표시·접근성 정리도 했다.
산만하지 않게, 한 일마다 배경지식 → 어떤 상황 → 핵심 작업 → 교훈 순서로 묶어 정리한다.
하루 요약
| 작업 | 무엇을 하고 싶었나 | 한 일 | 결과 |
|---|---|---|---|
| 1. 스크롤 연동 홈 연출 | JS 라이브러리 없이 "기록 → 승격" 스토리 스크롤 연출 | sticky 무대 + 진행도 phase, clip-path 반복 loop, view() 등장 | 라이브러리 0kb로 홈 내러티브 구현 |
| 2. 타이포 CLS 정리 | 느린 네트워크에서도 글자 밀림(CLS) 0 | 태그·배지·본문 폰트 swap → optional | 느린 네트워크에서 CLS 0 (회귀 테스트) |
| 3. UI 일관성 & 접근성 | 활성 표시 통일 + 대비 위반 해소 | 활성 레일 단일화·aria-current, 대비 AA 토큰화, VR baseline | 페이지 간 표시 통일, a11y 위반 해소 |
1. 스크롤 연동 홈 내러티브
배경지식 (개념)
- scrolly / sticky scrolly: 한 요소를
position: sticky로 붙여 둔 채 스크롤 양을 진행도로 바꿔, 스크롤하는 만큼 단계(phase)가 넘어가게 하는 연출. - scroll-timeline / view-timeline (
animation-timeline): JS 없이 스크롤 위치 자체를 애니메이션의 시간축으로 쓰는 CSS 기능.view()는 "요소가 뷰포트에 들어오는 정도"를 시간축으로 삼는다. - CSS keyframes:
@keyframes로 정의해 시간에 따라 도는 애니메이션. 스크롤과 무관하게 스스로 반복(loop)한다.
어떤 상황이었나
- 최근 랜딩 페이지 흐름은 JS 애니메이션 라이브러리 대신 CSS keyframes + 네이티브 scroll-timeline을 쓰는 쪽이 대세다.
- 문제는 시간 loop(스스로 반복) / 스크롤 phase(진행도로 단계 전환) / 스크롤 등장(view reveal) 세 축이 섞이면 서로 깨진다는 것.
- sticky scrolly와 view-timeline은 부모에
overflow: hidden한 줄만 있어도 동시에 조용히 죽는다.
핵심 작업
- 파이프라인 섹션은 sticky 무대 + 래퍼 높이 기반 진행도로 phase를 파생했다 — 한 화면에 한 단계, 레일과 그리드는 고정.
- 히어로 라이브 데모는 clip-path steps 와이프 +
animation-delay만으로 반복 loop를 만들었다. hydration(서버 HTML에 JS를 붙여 살리는 과정) 비용 0, 텍스트는 선명,min-height로 자리를 예약해 loop가 레이아웃을 흔들지 않게 했다. - 목록·카드 등장은
animation-timeline: view()+animation-range: entry로 진입하며 fade — loop와는 클래스·시간축을 분리했다. prefers-reduced-motion에서는 sticky를 해제하고 최종 승격 상태로 고정 → 애니만 끄고 정보는 그대로 degrade.
교훈
- 랜딩 모션은 JS 0kb로도 가능하다 — scroll-timeline·sticky·keyframes 조합이 최근 흐름과 맞는다.
- 세 시간축(시간 loop / 스크롤 phase / 스크롤 등장)은 클래스·타임라인으로 분리하지 않으면 서로 깨진다.
- sticky scrolly는 조상 요소의
overflow: hidden한 줄로 조용히 죽는다 — 깨지면 부모 체인부터 의심한다. - 장식용 데모는
aria-hidden+min-height예약으로 layout을 흔들지 않게 한다.
→ Scroll-driven narrative layout · CSS view-timeline entry animation · 2026 startup landing page trends
2. 타이포 CLS 정리
배경지식 (개념)
- CLS (Cumulative Layout Shift): 페이지가 로드되며 요소가 갑자기 밀려나는 정도. 낮을수록 좋다.
font-display: swapvsoptional:swap은 웹폰트가 늦게 도착하면 대체 폰트를 웹폰트로 바꾼다.optional은 아주 짧은 대기 안에 폰트가 안 오면 대체 폰트를 그대로 유지한다.
어떤 상황이었나
- 태그 pill·배지·본문이
font-display: swap이라, 웹폰트가 늦게 도착하면 글자 크기(glyph metrics)가 바뀌며 레이아웃이 밀렸다(CLS 튐).
핵심 작업
- 태그 pill·배지·본문 폰트를
swap→optional로 바꿨다. - 느린 네트워크 +
optional조합에서 CLS 0을 지키는 회귀 테스트를 추가했다.
교훈
font-display: optional은 브랜드 폰트의 완벽 재현보다 측정 가능한 CLS가 우선인 문서 UI에 맞다.
→ font-display optional and CLS
3. UI 일관성 & 접근성
배경지식 (개념)
aria-current: 현재 활성 항목(현재 페이지·필터)을 보조기술에 알리는 속성.- 대비 AA: WCAG 명암비 기준. 텍스트/배경 대비가 이 기준을 넘어야 저시력 사용자도 읽을 수 있다.
- VR(Visual Regression) baseline: 화면 스크린샷을 기준선으로 저장해 두고, 이후 변경이 화면을 의도치 않게 바꿨는지 픽셀 비교로 잡는 것.
어떤 상황이었나
- 활성 상태 표시(레일·밑줄·pill)가 페이지마다 달라 눈으로 훑는 비용이 컸다.
- 강조색의 텍스트 대비가 기준에 못 미치는 곳이 있었다.
핵심 작업
- 활성 표시를 레일 하나로 단일화하고, 필터에
aria-current를 붙이고, 본문 폭을 제한했다. - 강조색을 대비 AA 통과값으로 조정해
--signal-text토큰으로 뽑았다(재발 방지). - 주요 breakpoint에 대해 VR baseline을 잡았다. 타입체크 + 단위(vitest) + E2E 전부 green.
교훈
- 활성 표시는 단일 소스로 통일해야 페이지마다 어긋나지 않는다.
- 대비값은 토큰화해 두면 다음에 같은 위반이 재발하지 않는다.