sidenav 구성요소 빌드

반응형 슬라이드 아웃 사이드나비를 빌드하는 방법에 관한 기본 개요

이 게시물에서는 반응형이고 상태가 있으며 키보드 탐색을 지원하고 JavaScript 유무와 관계없이 작동하며 여러 브라우저에서 작동하는 웹용 사이드나브 구성요소의 프로토타입을 만드는 방법을 공유하고자 합니다. 데모를 사용해 보세요.

동영상을 선호하는 경우 이 게시물의 YouTube 버전을 참고하세요.

개요

반응형 탐색 시스템을 빌드하는 것은 쉽지 않습니다. 어떤 사용자는 키보드를 사용하고, 어떤 사용자는 강력한 데스크톱을 사용하고, 어떤 사용자는 작은 휴대기기에서 방문합니다. 방문하는 모든 사용자가 메뉴를 열고 닫을 수 있어야 합니다.

데스크톱에서 모바일로 반응형 레이아웃 데모
iOS 및 Android의 밝은 테마 및 어두운 테마 설정

웹 전술

이 구성요소 탐색에서는 몇 가지 중요한 웹 플랫폼 기능을 결합할 수 있었습니다.

  1. CSS :target
  2. CSS 그리드
  3. CSS 변환
  4. 표시 영역 및 사용자 환경설정에 관한 CSS 미디어 쿼리
  5. focus용 JS UX 개선

솔루션에는 사이드바가 하나 있으며 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개를 만드는 그리드이며, 각각 1개는 이름이 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>와 메뉴를 닫는 데 사용되는 이름이 [escape]인 배경 <a>입니다.

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

2fr1fr를 조정하여 메뉴 오버레이와 여백 닫기 버튼의 원하는 비율을 찾습니다.

비율을 변경할 때 어떤 일이 일어나는지 보여주는 데모입니다.

CSS 3D 변환 및 전환

이제 레이아웃이 모바일 뷰포트 크기로 쌓입니다. 새로운 스타일을 추가할 때까지는 기본적으로 도움말 위에 겹쳐 표시됩니다. 다음 섹션에서 추구하는 UX는 다음과 같습니다.

  • 열기 및 닫기 애니메이션
  • 사용자가 동의하는 경우에만 모션으로 애니메이션 처리
  • 키보드 포커스가 화면 밖 요소로 이동하지 않도록 visibility에 애니메이션 적용

모션 애니메이션을 구현하기 시작할 때 접근성을 최우선으로 생각하고 시작하려고 합니다.

접근 가능한 모션

슬라이드 아웃 모션 환경을 원하는 사용자는 많지 않습니다. 이 솔루션에서는 미디어 쿼리 내에서 --duration CSS 변수를 조정하여 이 환경설정을 적용합니다. 이 미디어 쿼리 값은 사용자의 모션에 대한 운영체제 환경설정을 나타냅니다 (가능한 경우).

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
적용된 지속 시간 유무와 관계없는 상호작용의 데모입니다.

이제 사이드나비가 열리고 닫힐 때 사용자가 움직임을 줄이고 싶다면 요소를 즉시 뷰로 이동하여 움직임 없이 상태를 유지합니다.

전환, 변환, 번역

사이드나브 아웃 (기본값)

휴대기기에서 사이드나브의 기본 상태를 오프스크린 상태로 설정하려면 transform: translateX(-110vw)를 사용하여 요소를 배치합니다.

사이드바의 box-shadow가 숨겨져 있을 때 기본 뷰포트를 들여다보지 않도록 하기 위해 일반적인 오프스크린 코드 -100vw10vw를 하나 더 추가했습니다.

@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 요소가 :target과 일치하면 translateX() 위치를 홈베이스 0로 설정하고 CSS가 URL 해시가 변경될 때 요소를 -110vw의 외부 위치에서 var(--duration) 위의 0의 '내부' 위치로 슬라이드하는 것을 확인합니다.

@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>
음성 해설 및 키보드 상호작용 UX의 데모입니다.

이제 기본 상호작용 버튼에 마우스와 키보드 모두에 대한 의도가 명확하게 표시됩니다.

: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를 만들고 내 버전을 트윗해 주시면 아래의 커뮤니티 리믹스 섹션에 추가해 드리겠습니다.

커뮤니티 리믹스