반응형 슬라이드 아웃 사이드 탐색 메뉴를 빌드하는 방법에 관한 기본 개요
이 게시물에서는 반응형이고, 상태를 유지하며, 키보드 탐색을 지원하고, JavaScript 유무에 관계없이 작동하며, 여러 브라우저에서 작동하는 웹용 Sidenav 구성요소를 프로토타입으로 만드는 방법을 공유하고자 합니다. 데모를 사용해 보세요.
동영상을 선호하는 경우 이 게시물의 YouTube 버전을 확인하세요.
개요
반응형 탐색 시스템을 구축하는 것은 어렵습니다. 일부 사용자는 키보드를 사용하고, 일부는 강력한 데스크톱을 사용하며, 일부는 작은 휴대기기에서 방문합니다. 방문하는 모든 사용자가 메뉴를 열고 닫을 수 있어야 합니다.
웹 전술
이 구성요소 탐색에서는 다음과 같은 몇 가지 중요한 웹 플랫폼 기능을 결합할 수 있었습니다.
내 솔루션에는 사이드바가 하나 있으며 540px
이하의 '모바일' 표시 영역에 있을 때만 전환됩니다.
540px
은 모바일 대화형 레이아웃과 정적 데스크톱 레이아웃 간 전환을 위한 중단점이 됩니다.
CSS :target
가상 클래스
한 <a>
링크는 URL 해시를 #sidenav-open
로 설정하고 다른 링크는 비어 있는 상태 (''
)로 설정합니다. 마지막으로 요소에는 해시와 일치하는 id
가 있습니다.
<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<aside id="sidenav-open">
…
</aside>
이러한 링크를 클릭하면 페이지 URL의 해시 상태가 변경되고, 의사 클래스를 사용하여 사이드 탐색 메뉴를 표시하거나 숨깁니다.
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
CSS 그리드
이전에는 절대 또는 고정 위치 탐색 메뉴 레이아웃과 구성요소만 사용했습니다. 하지만 그리드는 grid-area
구문을 사용하여 여러 요소를 동일한 행이나 열에 할당할 수 있습니다.
스택
기본 레이아웃 요소 #sidenav-container
는 1개의 행과 2개의 열을 만드는 그리드이며, 각각 stack
라는 이름이 지정됩니다. 공간이 제한되면 CSS는 모든 <main>
요소의 하위 요소를 동일한 그리드 이름에 할당하여 모든 요소를 동일한 공간에 배치하고 스택을 만듭니다.
#sidenav-container {
display: grid;
grid: [stack] 1fr / min-content [stack] 1fr;
min-height: 100vh;
}
@media (max-width: 540px) {
#sidenav-container > * {
grid-area: stack;
}
}
메뉴 배경
<aside>
는 측면 탐색이 포함된 애니메이션 요소입니다. 여기에는 2개의 하위 요소가 있습니다. 탐색 컨테이너 <nav>
(이름: [nav]
)와 메뉴를 닫는 데 사용되는 배경 <a>
(이름: [escape]
)입니다.
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
2fr
및 1fr
을 조정하여 메뉴 오버레이와 여백 닫기 버튼에 적합한 비율을 찾습니다.
CSS 3D 변환 및 전환
이제 레이아웃이 모바일 표시 영역 크기로 스택됩니다. 새 스타일을 추가할 때까지는 기본적으로 도움말이 오버레이됩니다. 다음 섹션에서 목표로 하는 UX는 다음과 같습니다.
- 열기 및 닫기 애니메이션
- 사용자가 동의하는 경우에만 동작으로 애니메이션 처리
- 키보드 포커스가 화면 밖 요소로 들어가지 않도록
visibility
에 애니메이션 적용
동작 애니메이션을 구현할 때 접근성을 최우선으로 고려하고 싶습니다.
접근성 모션
모든 사용자가 슬라이드 아웃 동작 환경을 원하는 것은 아닙니다. Google 솔루션에서는 미디어 쿼리 내에서 --duration
CSS 변수를 조정하여 이 환경설정을 적용합니다. 이 미디어 쿼리 값은 동작에 대한 사용자의 운영체제 환경설정을 나타냅니다 (사용 가능한 경우).
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
이제 사이드 탐색 메뉴가 슬라이드되어 열리고 닫힐 때 사용자가 모션 감소를 선호하는 경우 모션 없이 상태를 유지하면서 요소를 즉시 뷰로 이동합니다.
전환, 변환, 번역
사이드 탐색 메뉴 닫기 (기본값)
휴대기기에서 사이드 탐색의 기본 상태를 화면 밖 상태로 설정하기 위해 transform: translateX(-110vw)
로 요소를 배치합니다.
사이드 탐색의 box-shadow
가 숨겨져 있을 때 기본 뷰포트로 엿보지 않도록 일반적인 -100vw
오프스크린 코드에 10vw
를 하나 더 추가했습니다.
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
transform: translateX(-110vw);
will-change: transform;
transition:
transform var(--duration) var(--easeOutExpo),
visibility 0s linear var(--duration);
}
}
Sidenav in
#sidenav
요소가 :target
로 일치하면 translateX()
위치를 홈베이스 0
로 설정하고 URL 해시가 변경되면 CSS가 요소의 'out' 위치인 -110vw
에서 'in' 위치인 0
로 var(--duration)
에 걸쳐 슬라이드하는 것을 확인합니다.
@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}
전환 공개 상태
이제 목표는 메뉴가 표시되지 않을 때 스크린 리더에서 메뉴를 숨겨 시스템이 오프스크린 메뉴에 포커스를 두지 않도록 하는 것입니다. :target
가 변경될 때 표시 상태 전환을 설정하여 이를 달성합니다.
- 진입할 때는 표시 상태를 전환하지 마세요. 요소가 슬라이드되고 포커스를 수락할 수 있도록 바로 표시되어야 합니다.
- 나갈 때는 전환 가시성을 전환하되 지연하여 전환이 끝날 때
hidden
로 전환합니다.
접근성 UX 개선
링크
이 솔루션은 상태를 관리하기 위해 URL을 변경하는 데 의존합니다.
여기에서는 <a>
요소를 사용해야 하며, 접근성 기능이 무료로 제공됩니다. 의도를 명확하게 설명하는 라벨로 대화형 요소를 꾸며 보겠습니다.
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>
<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
<svg>...</svg>
</a>
이제 기본 상호작용 버튼은 마우스와 키보드 모두에 대해 의도를 명확하게 나타냅니다.
:is(:hover, :focus)
이 유용한 CSS 함수형 의사 선택기를 사용하면 포커스와 함께 공유하여 마우스 오버 스타일을 신속하게 포괄적으로 만들 수 있습니다.
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
JavaScript 추가
escape
을 눌러 닫기
키보드의 Escape
키를 누르면 메뉴가 닫혀야 하지 않나요? 연결해 보겠습니다.
const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
if (event.code === 'Escape') document.location.hash = '';
});
브라우저 방문 기록
열기 및 닫기 상호작용으로 인해 브라우저 기록에 여러 항목이 누적되지 않도록 닫기 버튼에 다음 JavaScript를 인라인으로 추가합니다.
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>
이렇게 하면 닫을 때 URL 기록 항목이 삭제되어 메뉴가 열린 적이 없는 것처럼 됩니다.
집중 모드 UX
다음 스니펫은 열기 및 닫기 버튼이 열리거나 닫힌 후 버튼에 포커스를 두는 데 도움이 됩니다. 전환을 쉽게 하고 싶습니다.
sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';
isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})
사이드 탐색 메뉴가 열리면 닫기 버튼에 포커스를 둡니다. 사이드 탐색 메뉴가 닫히면 열기 버튼에 포커스를 둡니다. JavaScript에서 요소에 focus()
를 호출하여 이 작업을 실행합니다.
결론
이제 제가 어떻게 했는지 아셨으니, 여러분은 어떻게 하시겠어요? 이로 인해 재미있는 구성요소 아키텍처가 만들어집니다. 슬롯이 있는 첫 번째 버전을 누가 만들 건가요? 🙂
다양한 접근 방식을 통해 웹에서 빌드하는 모든 방법을 알아보세요. Glitch를 만들고 버전을 트윗하면 아래의 커뮤니티 리믹스 섹션에 추가해 드립니다.
커뮤니티 리믹스
- 커스텀 요소를 사용하는 @_developit: 데모 및 코드
- @mayeedwin1(HTML/CSS/JS 사용): 데모 및 코드
- @a_nurella의 글리치 리믹스: 데모 및 코드
- HTML/CSS/JS를 사용하는 @EvroMalarkey: 데모 및 코드