sidenav 구성요소 빌드

반응형 슬라이드 아웃 사이드 탐색 메뉴를 빌드하는 방법에 관한 기본 개요

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

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

개요

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

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

웹 전술

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

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

내 솔루션에는 사이드바가 하나 있으며 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;
}

2fr1fr을 조정하여 메뉴 오버레이와 여백 닫기 버튼에 적합한 비율을 찾습니다.

비율을 변경할 때 발생하는 상황을 보여주는 데모입니다.

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' 위치인 0var(--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>
음성 해설 및 키보드 상호작용 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를 만들고 버전을 트윗하면 아래의 커뮤니티 리믹스 섹션에 추가해 드립니다.

커뮤니티 리믹스