스와이프하여 표시 패턴을 사용하면 사용자가 전경 행 또는 콘텐츠 창을 가로로 슬라이드하여 숨겨진 UI(예: 상황별 작업 버튼(고정, 음소거, 보관처리, 삭제) 또는 보조 측면 메뉴)를 표시할 수 있습니다. 작업은 자동으로 활성화되지 않습니다. 스와이프 중에 작업을 실행하는 대신 컨테이너가 스냅되어 열리면서 사용자가 검사하고 활성화하거나 다시 스와이프하여 닫을 수 있는 대화형 컨트롤이 표시됩니다.
기준점을 지나 항목을 즉시 커밋하고 닫는 전체 너비 스와이프 동작은 스와이프하여 삭제를 참고하세요. 세로 스크롤 컨테이너 상단에 숨겨진 헤더를 표시하려면 당겨서 표시를 참고하세요.
구현 방법
목록 항목의 작업 버튼을 표시하든 스크롤 가능한 창 옆의 사이드 메뉴를 표시하든 메커니즘은 동일합니다. CSS Scroll Snap (scroll-snap-type: x mandatory) 및 overscroll-behavior: chain로 구성된 외부 가로 스크롤 컨테이너 내에 포그라운드 콘텐츠와 숨겨진 패널을 래핑합니다. 브라우저의 네이티브 스크롤 엔진은 JavaScript 동작 리스너 없이 모멘텀, 맞추기, 키보드 포커스 스크롤, 스크롤 체이닝을 처리합니다.
1단계: 스와이프 래퍼, 콘텐츠, 표시된 패널 마크업하기
.SwipeReveal 스크롤 컨테이너 내부에 전경 콘텐츠를 먼저 배치하고 표시된 컨트롤 (작업 버튼 또는 측면 메뉴)을 두 번째로 배치합니다. 콘텐츠가 DOM 순서에서 첫 번째이므로 스크롤 컨테이너는 자연스럽게 스크롤 오프셋 0에서 시작되며 패널은 화면 오른쪽에서 숨겨집니다.
<!-- Example A: List item with revealable action buttons -->
<div class="SwipeReveal">
<div class="SwipeReveal-content">
<strong>Design review notes</strong>
<p>Updated component specs for the Q3 release.</p>
</div>
<div class="SwipeReveal-panel" role="group" aria-label="Item actions">
<button type="button">Pin</button>
<button type="button">Archive</button>
<button type="button">Delete</button>
</div>
</div>
<!-- Example B: Scrollable content pane with a revealable side menu -->
<div class="SwipeReveal" role="region" aria-label="Document preview with swipeable side menu">
<div class="SwipeReveal-content is-scrollable" tabindex="0">
<h2>Article Preview</h2>
<p>Scroll vertically through this pane, or swipe left to reveal the side menu...</p>
</div>
<nav class="SwipeReveal-panel" aria-label="Contextual menu">
<a href="#share">Share</a>
<a href="#bookmark">Bookmark</a>
<a href="#export">Export PDF</a>
</nav>
</div>
2단계: 가로 스냅 컨테이너 및 overscroll-behavior: chain 구성
스와이프하여 표시 효과를 만들기 위해서만 외부 스크롤 컨테이너에 요소를 래핑하면 외부 래퍼가 문서 (또는 다른 상위 스크롤 컨테이너) 내의 중간 스크롤러가 됩니다.
overscroll-behavior의 네 가지 키워드는 초과 스크롤이 상위 스크롤 컨테이너로 전파되는지 (스크롤 체이닝)와 컨테이너가 탄력적인 고무줄 늘이기 또는 가장자리 발광과 같은 로컬 오버스크롤 어포던스 (로컬 경계 효과)를 표시하는지 여부를 독립적으로 제어합니다.
| 값 | 스크롤 체이닝 (상위 요소로 전파) | 로컬 경계 효과 (고무줄/글로우) | 스와이프하여 표시하기에서 역할 |
|---|---|---|---|
auto |
예 | 예 | 기본 동작입니다. 일반적인 바운스 피드백이 필요한 내부 콘텐츠 스크롤러에서는 이 동작을 유지하되, 메뉴가 바운스되는 외부 스와이프 래퍼에서는 이 동작을 피하세요. |
chain |
예 | 아니요 | 메뉴나 작업 스트립이 바운스되지 않고 초과 스크롤이 상위 스크롤러 (예: 문서)로 체이닝되도록 외부 .SwipeReveal 스크롤러에 사용 |
contain |
아니요 | 예 | 로컬에서 바운스하면서 컨테이너 내에서 스크롤을 트랩합니다. |
none |
아니요 | 아니요 | 로컬 바운스와 상위 요소로의 스크롤 체이닝을 모두 차단합니다. |
.SwipeReveal를 2열 그리드 (grid-template-columns: 100% max-content)로 만듭니다. 여기서 .SwipeReveal-content는 start에 맞추고 .SwipeReveal-panel는 end에 맞춥니다.
.SwipeReveal {
/* Column 1: full-width content. Column 2: auto-sized actions or side menu. */
display: grid;
grid-template-columns: 100% max-content;
overflow-x: auto;
scroll-snap-type: x mandatory;
scrollbar-width: none;
/* Prevent the swipe wrapper and revealed panel from bouncing locally while
still chaining excess scroll into the parent scroller (the document). */
overscroll-behavior: chain;
}
@media (prefers-reduced-motion: no-preference) {
.SwipeReveal {
scroll-behavior: smooth;
}
}
.SwipeReveal-content {
scroll-snap-align: start;
scroll-initial-target: nearest;
background: Canvas;
}
.SwipeReveal-content:focus-visible {
outline: auto;
outline-offset: -2px;
}
.SwipeReveal-content.is-scrollable {
block-size: 20rem;
overflow: auto;
/* Keeps default `overscroll-behavior: auto` so the content pane retains
its normal bounce effect while chaining horizontal swipes to `.SwipeReveal`. */
}
.SwipeReveal-panel {
/* Snapping a trailing panel to `end` (or a leading panel to `start`) brings
all controls fully into view while keeping the adjacent edge of the content visible. */
scroll-snap-align: end;
display: flex;
&:first-child {
scroll-snap-align: start;
}
}
3열 (
grid-template-columns: max-content 100% max-content) 또는 2열 (max-content 100%)을 사용하여 왼쪽 (inline-start)에 컨트롤을 표시하려면.SwipeReveal-content앞에 선행.SwipeReveal-panel를 배치합니다..SwipeReveal-content에scroll-initial-target: nearest가 있으므로 스크롤 컨테이너는 처음에 선행 패널이 아닌 콘텐츠 열에 놓입니다.
3단계: 키보드 포커스 시 포커스 가능한 콘텐츠 창을 다시 보기로 맞추기
.SwipeReveal-panel 내부의 버튼이나 링크는 정지 상태에서 화면 밖에 있으므로 키보드로 탭하면 .SwipeReveal가 자동으로 스크롤되어 포커스가 맞춰진 컨트롤이 표시됩니다. 하지만 .SwipeReveal-content는 외부 컨테이너 너비의 100%에 걸쳐 있으므로 측면 패널이 열려 있는 동안에도 대부분이 스크롤포트 내에 남아 있습니다. 따라서 Shift+Tab를 눌러 포커스를 .SwipeReveal-content (또는 내부의 포커스 가능 요소)로 다시 이동해도 트랙이 0로 자동으로 다시 스크롤되지 않습니다.
.SwipeReveal-content에서 focusin를 수신 대기하고 event.target.matches(':focus-visible')를 확인하여 창 또는 하위 요소의 키보드 포커스가 포인터 클릭이나 터치 드래그를 방해하지 않고 .SwipeReveal-content 시작 부분으로 다시 돌아가도록 합니다.
for (const content of document.querySelectorAll('.SwipeReveal-content')) {
content.addEventListener('focusin', (event) => {
if (event.target.matches(':focus-visible')) {
content.scrollIntoView({
block: 'nearest',
inline: 'start',
});
}
});
}
권장사항 및 주의사항
proximity대신scroll-snap-type: x mandatory를 사용합니다.proximity를 사용하면 가볍게 스와이프해도 작업 버튼이나 측면 메뉴가 정지 상태에서 부분적으로 노출될 수 있습니다.- 권장사항: 외부 가로 스와이프하여 표시 스크롤러 (
.SwipeReveal)에overscroll-behavior: chain를 설정하여 표시된 메뉴나 작업 스트립이 바운스되지 않고 스크롤이 상위 스크롤러에 연결될 수 있도록 합니다. - 권장사항: 내부 스크롤 가능 콘텐츠 창(
.SwipeReveal-content.is-scrollable)에서overscroll-behavior: auto(기본값)를 그대로 둡니다. 이 경우 일반적인 바운스 피드백이 발생하면서도 가로 스와이프가 외부 스와이프 스크롤러에 연결됩니다. - 표시된 작업과 측면 메뉴 항목을 실제 포커스 가능한 DOM 컨트롤(
<button>,<a>)로 구현해야 합니다(DO)..SwipeReveal-content가 포커스 가능하거나 포커스 가능한 컨트롤을 포함하는 경우focusin리스너를event.target.matches(':focus-visible')와 페어링하여scrollIntoView({ block: 'nearest', inline: 'start' })를 호출합니다. 그러면 콘텐츠 창으로 다시 탭하면 포인터 상호작용에 영향을 주지 않고 측면 패널이 닫힙니다. - JavaScript에서
wheel,touchmove또는pointermove이벤트를 가로채서 콘텐츠를 수동으로 변환하거나 스크롤 체이닝을 에뮬레이션하지 마세요. 기본 스레드 동작 가로채기는 컴포지터 기반 스크롤을 차단하고, 네이티브 스크롤 모멘텀을 중단하며, 응답성을 저하시킵니다.
대체
.SwipeReveal 컨테이너가 .SwipeReveal-content 앞에 .SwipeReveal-panel를 왼쪽 (inline-start)에 배치하면 scroll-initial-target: nearest를 지원하지 않는 브라우저는 처음에 선행 패널이 노출된 상태로 스크롤 오프셋 0에서 렌더링됩니다.
기준 타겟이 scroll-initial-target를 지원하지 않는 경우 CSS.supports('scroll-initial-target', 'nearest')로 지원을 감지하고 load에서 behavior: 'instant'로 scrollTo()를 호출하여 선행 패널이 있는 컨테이너가 페이지를 스크롤하지 않고 바로 .SwipeReveal-content로 이동하도록 합니다.
점진적 개선
overscroll-behavior: chain 및 scrollbar-width: none를 JavaScript 대체 없이 점진적 개선으로 처리합니다.
- 자동 CSS 계단식 대체:
overscroll-behavior의 초깃값은auto입니다. 아직chain키워드를 인식하지 못하는 브라우저는 파싱 시overscroll-behavior: chain를 무시하고auto를 유지합니다.auto는 이미 상위 스크롤 컨테이너로의 스크롤 체이닝을 허용하므로 스와이프하여 표시 동작과 상위 요소로의 스크롤 체이닝이 모든 브라우저에서 기본적으로 작동합니다. 지원하는 브라우저는 외부 스와이프 래퍼에서 원치 않는 고무줄 바운스를 억제할 뿐입니다. contain또는none재정의: 기본 스타일시트 또는 유틸리티 클래스가overscroll-behavior: contain또는none를 설정하는 경우overscroll-behavior: chain바로 앞에overscroll-behavior: auto를 선언하여 아직chain를 지원하지 않는 브라우저가 스크롤을 가두는 대신 상위 요소에 연결하도록 합니다.- 스크롤바 숨기기 (
scrollbar-width: none): 가로 스크롤바 트랙을 숨기는 것은 순전히 미적인 이유입니다. 이전 엔진이scrollbar-width: none를 무시하는 경우 스와이프하여 표시 컨테이너는 표준 스크롤바를 사용하여 완전히 작동합니다.