sidenav 구성요소 빌드

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

이 게시물에서는 반응형, 스테이트풀(Stateful)이고, 키보드 탐색을 지원하고, JavaScript와 관계없이 작동하고, 여러 브라우저에서 작동하는 Sidenav 구성요소의 프로토타입을 제작한 방법을 공유하고자 합니다. 데모 사용해 보기

동영상을 선호한다면 이 게시물의 YouTube 버전을 참고하세요.

개요

반응형 내비게이션 시스템을 빌드하는 것은 어렵습니다. 키보드를 사용하는 사용자도 있고 성능이 우수한 데스크톱을 사용하는 사용자도 있고 작은 휴대기기를 통해 방문하는 사용자도 있습니다. 방문하는 모든 사람이 메뉴를 열고 닫을 수 있어야 합니다.

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

웹 전략

이 구성요소 탐색에서는 몇 가지 중요한 웹 플랫폼 기능을 결합하는 즐거움을 느꼈습니다.

  1. CSS :target
  2. CSS 그리드
  3. CSS transforms
  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의 해시 상태가 변경된 다음 의사 클래스를 사용하여 sidenav를 표시하고 숨깁니다.

@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>와 메뉴를 닫는 데 사용되는 [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)로 요소를 배치합니다.

참고: 일반적인 오프스크린 코드 -100vw에 또 다른 10vw를 추가하여 sidenav의 box-shadow가 숨겨져 있을 때 기본 표시 영역을 보지 않도록 했습니다.

@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 'in' 위치로 요소를 슬라이드하는 것을 확인합니다.

@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를 만들고 내 버전을 트윗하여 아래 커뮤니티 리믹스 섹션에 추가합니다.

커뮤니티 리믹스