탐색경로 구성요소 빌드

사용자가 사이트를 탐색할 수 있도록 반응하며 액세스 가능한 탐색경로 구성요소를 빌드하는 방법에 관한 기본적인 개요입니다.

이 게시물에서는 탐색경로 구성요소를 빌드하는 방법에 관한 생각을 공유하고자 합니다. 데모 사용해 보기

데모

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

개요

탐색경로 구성요소는 사이트 계층 구조에서 사용자의 위치를 보여줍니다. 이 이름은 헨젤과 그레텔에서 유래했습니다. 헨젤과 그레텔은 어두운 숲에서 탐색경로를 떨어뜨렸고 부스러기를 뒤로 추적하여 집으로 가는 길을 찾을 수 있었습니다.

이 게시물의 탐색경로는 표준 탐색경로가 아니며 탐색경로와 유사합니다. <select>를 사용하여 동위 페이지를 탐색에 바로 배치하여 추가 기능을 제공하므로 다중 계층 액세스가 가능합니다.

백그라운드 UX

위의 구성요소 데모 동영상에서 자리표시자 카테고리는 비디오 게임의 장르입니다. 이 트레일은 아래와 같이 home » rpg » indie » on sale 경로를 탐색하여 생성됩니다.

이 탐색경로 구성요소를 통해 사용자는 이러한 정보 계층 구조를 통해 이동할 수 있어야 합니다. 즉, 브랜치를 이동하고 페이지를 정확하고 빠르게 선택할 수 있어야 합니다.

정보 아키텍처

컬렉션과 아이템의 관점에서 생각하는 것이 도움이 됩니다.

컬렉션

컬렉션은 선택할 수 있는 옵션의 배열입니다. 이 게시물의 탐색경로 프로토타입 홈페이지에서 컬렉션은 FPS, RPG, 브롤러, 던전 크롤러, 스포츠, 퍼즐입니다.

항목

비디오 게임은 아이템이며, 특정 컬렉션이 다른 컬렉션을 나타내는 경우 아이템이 될 수도 있습니다. 예를 들어 RPG는 아이템이자 유효한 컬렉션입니다. 상품일 경우 사용자는 해당 컬렉션 페이지에 있습니다. 예를 들어 AAA, 인디, 자체 게시라는 추가 하위 카테고리 등 RPG 게임 목록이 표시되는 RPG 페이지에 있습니다.

컴퓨터 공학 측면에서 이 탐색경로 구성요소는 다차원 배열을 나타냅니다.

const rawBreadcrumbData = {
  "FPS": {...},
  "RPG": {
    "AAA": {...},
    "indie": {
      "new": {...},
      "on sale": {...},
      "under 5": {...},
    },
    "self published": {...},
  },
  "brawler": {...},
  "dungeon crawler": {...},
  "sports": {...},
  "puzzle": {...},
}

앱이나 웹사이트에는 다양한 다차원 배열을 만드는 맞춤 정보 아키텍처 (IA)가 있지만 컬렉션 방문 페이지 및 계층 구조 순회라는 개념이 탐색경로에도 포함되기를 바랍니다.

레이아웃

마크업

좋은 구성요소는 적절한 HTML에서 시작합니다. 다음 섹션에서는 마크업 선택과 이러한 마크업이 전체 구성요소에 어떤 영향을 미치는지 알아봅니다.

어둡고 밝은 색 구성표

<meta name="color-scheme" content="dark light">

위 스니펫의 color-scheme 메타 태그는 이 페이지에서 밝은 브라우저 스타일과 어두운 브라우저 스타일을 원한다고 브라우저에 알립니다. 탐색경로 예에는 이러한 색 구성표의 CSS가 포함되어 있지 않으므로 탐색경로에는 브라우저에서 제공하는 기본 색상이 사용됩니다.

<nav class="breadcrumbs" role="navigation"></nav>

암시적 ARIA 탐색 역할이 있는 사이트 탐색에는 <nav> 요소를 사용하는 것이 좋습니다. 테스트 과정에서 role 속성을 사용하면 스크린 리더가 요소와 상호작용하는 방식이 변경되었으며, 이 속성이 탐색으로 표시되었으므로 이 속성을 추가하기로 결정했습니다.

아이콘

페이지에서 아이콘이 반복될 때 SVG <use> 요소는 path를 한 번 정의하여 아이콘의 모든 인스턴스에 사용할 수 있음을 의미합니다. 이렇게 하면 동일한 경로 정보가 반복되지 않아 문서 크기가 커지고 경로 불일치가 발생할 수 있습니다.

이 기법을 사용하려면 숨겨진 SVG 요소를 페이지에 추가하고 고유 ID를 사용하여 <symbol> 요소에 아이콘을 래핑합니다.

<svg style="display: none;">

  <symbol id="icon-home">
    <title>A home icon</title>
    <path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
  </symbol>

  <symbol id="icon-dropdown-arrow">
    <title>A down arrow</title>
    <path d="M19 9l-7 7-7-7"/>
  </symbol>

</svg>

브라우저는 SVG HTML을 읽고 아이콘 정보를 메모리에 넣고 다음과 같이 아이콘을 더 사용하기 위해 페이지의 나머지 부분을 계속 ID를 참조합니다.

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-home" />
</svg>

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-dropdown-arrow" />
</svg>

렌더링된 SVG 사용 요소를 보여주는 DevTools

페이지 성능 영향을 최소화하고 유연한 스타일 지정을 통해 한 번 정의하고 원하는 만큼 사용할 수 있습니다. aria-hidden="true"가 SVG 요소에 추가되었음을 알 수 있습니다. 아이콘은 콘텐츠만 들을 수 있는 탐색 중인 사용자에게 유용하지 않으며, 이러한 사용자에게 아이콘을 숨기면 불필요한 노이즈를 추가하지 않습니다.

여기에서 기존의 탐색경로와 이 구성요소의 탐색경로가 달라집니다. 일반적으로 이는 <a> 링크일 뿐이지만 위장 선택이 포함된 순회 UX를 추가했습니다. .crumb 클래스는 링크와 아이콘의 배치를 담당하고 .crumbicon는 아이콘과 선택 요소를 함께 쌓는 역할을 합니다. 분할 링크라고 부르는 이유는 기능이 분할 버튼과 매우 유사하기 때문입니다. 하지만 이는 페이지 탐색을 위한 것입니다.

<span class="crumb">
  <a href="#sub-collection-b">Category B</a>
  <span class="crumbicon">
    <svg>...</svg>
    <select class="disguised-select" title="Navigate to another category">
      <option>Category A</option>
      <option selected>Category B</option>
      <option>Category C</option>
    </select>
  </span>
</span>

링크와 일부 옵션은 특별하지 않지만 간단한 탐색경로에 더 많은 기능을 추가합니다. title<select> 요소에 추가하면 스크린 리더 사용자에게 버튼의 작업에 관한 정보를 제공하는 데 도움이 됩니다. 그러나 다른 모든 사용자에게도 동일한 도움을 제공하므로 iPad에서 앞 중앙에 표시됩니다. 한 속성은 여러 사용자에게 버튼 컨텍스트를 제공합니다.

보이지 않는 선택 요소에 마우스를 가져가면 표시되는 상황별 도움말을 보여주는 스크린샷

구분선 장식

<span class="crumb-separator" aria-hidden="true">→</span>

구분자는 선택사항이므로 하나만 추가하면 됩니다 (위 동영상의 세 번째 예 참고). 각 aria-hidden="true"은 장식용이기 때문에 스크린 리더가 알려주어야 하는 항목이 아니기 때문입니다.

다음에 다룰 gap 속성을 사용하면 이러한 간격을 간단하게 설정할 수 있습니다.

스타일

색상은 시스템 색상을 사용하므로 대부분 스타일의 간격과 스택입니다.

레이아웃 방향 및 흐름

Flexbox 오버레이 기능과 탐색경로 탐색 정렬을 보여주는 DevTools

기본 탐색 요소 nav.breadcrumbs는 하위 요소가 사용할 범위가 지정된 맞춤 속성을 설정하고 그 외에는 세로로 정렬된 가로 레이아웃을 설정합니다. 이렇게 하면 부스러기, 구분선, 아이콘이 정렬됩니다.

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

Flexbox 오버레이에 맞춰 세로로 정렬된 탐색경로 1개.

.crumb은 약간의 간격이 있는 세로로 정렬된 가로 레이아웃도 설정하지만 특별히 링크 하위 요소를 타겟팅하고 white-space: nowrap 스타일을 지정합니다. 여러 단어로 된 탐색경로에 여러 줄로 표시되는 것을 원하지 않기 때문에 이는 매우 중요합니다. 이 게시물의 후반부에서는 이 white-space 속성으로 인한 가로 오버플로를 처리하는 스타일을 추가합니다.

.crumb {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--nav-gap) / 4);

  & > a {
    white-space: nowrap;

    &[aria-current="page"] {
      font-weight: bold;
    }
  }
}

aria-current="page"가 추가되어 현재 페이지 링크가 나머지 링크보다 눈에 띄게 됩니다. 스크린 리더 사용자는 링크가 현재 페이지로 연결되는 링크임을 명확하게 알 수 있을 뿐만 아니라 시각장애인 사용자에게도 유사한 사용자 환경을 제공할 수 있도록 요소의 시각적으로 스타일을 지정했습니다.

.crumbicon 구성요소는 그리드를 사용하여 SVG 아이콘을 '거의 보이지 않는' <select> 요소와 쌓습니다.

행과 열이 모두 스택 이름인 버튼을 오버레이하는 Grid DevTools를 표시합니다.

.crumbicon {
  --crumbicon-size: 3ch;

  display: grid;
  grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
  place-items: center;

  & > * {
    grid-area: stack;
  }
}

<select> 요소는 DOM의 마지막 요소이므로 스택 맨 위에 있으며 상호작용이 가능합니다. 요소를 계속 사용할 수 있도록 opacity: .01 스타일을 추가합니다. 그러면 아이콘 모양에 완벽하게 맞는 선택 상자가 생성됩니다. 이는 내장 기능을 유지하면서 <select> 요소의 모양을 맞춤설정하는 좋은 방법입니다.

.disguised-select {
  inline-size: 100%;
  block-size: 100%;
  opacity: .01;
  font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}

오버플로

탐색경로는 매우 긴 흔적을 나타낼 수 있어야 합니다. 필요한 경우 화면 밖으로 가로로 이동할 수 있는 것을 좋아하며 이 탐색경로 구성요소에 적합하다고 생각했습니다.

.breadcrumbs {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: calc(var(--nav-gap) / 2);

  & > .crumb:last-of-type {
    scroll-snap-align: end;
  }

  @supports (-webkit-hyphens:none) { & {
    scroll-snap-type: none;
  }}
}

오버플로 스타일은 다음 UX를 설정합니다.

  • 오버스크롤 억제와 함께 가로 스크롤.
  • 가로 스크롤 패딩입니다.
  • 마지막 부스러기에 스냅 포인트 1개가 있습니다. 즉, 페이지 로드 시 첫 번째 크럼 로드가 맞춰져 뷰에 표시됩니다.
  • 가로 스크롤 및 맞추기 효과 조합으로 어려움을 겪는 Safari에서 맞추기 지점을 삭제합니다.

미디어 쿼리

작은 표시 영역을 조정하는 한 가지 미세한 조정은 아이콘만 남겨두고 '홈' 라벨을 숨기는 것입니다.

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

홈 라벨이 있는 탐색경로와 없는 탐색경로를 나란히 놓고 비교하기

접근성

움직임

이 구성요소에는 모션이 많지 않지만 prefers-reduced-motion 검사에서 전환을 래핑하여 원치 않는 모션을 방지할 수 있습니다.

@media (prefers-reduced-motion: no-preference) {
  .crumbicon {
    transition: box-shadow .2s ease;
  }
}

다른 스타일은 변경할 필요가 없습니다. 마우스 오버 및 포커스 효과는 transition가 없어도 훌륭하고 의미가 있습니다. 하지만 모션이 괜찮다면 상호작용에 미세한 전환을 추가합니다.

JavaScript

첫째, 사이트 또는 애플리케이션에서 사용하는 라우터 유형에 관계없이 사용자가 탐색경로를 변경하면 URL을 업데이트해야 하며 사용자가 적절한 페이지를 표시해야 합니다. 둘째, 사용자 환경을 정규화하려면 사용자가 <select> 옵션만 탐색할 때 예기치 않은 탐색이 발생하지 않도록 해야 합니다.

JavaScript에서 처리해야 하는 두 가지 중요한 사용자 환경 조치입니다. 선택이 변경되었고 즉시 <select> 변경 이벤트 실행 방지가 적용되었습니다.

즉시 실행(eager) 이벤트 방지는 <select> 요소를 사용하기 때문에 필요합니다. Windows Edge 및 다른 브라우저에서도 사용자가 키보드로 옵션을 탐색할 때 select changed 이벤트가 실행됩니다. 이것이 바로 Eager라고 부르는 이유입니다. 사용자가 마우스 오버 또는 포커스와 같은 유사 옵션만 선택했지만 enter 또는 click로 선택을 확인하지 않았기 때문입니다. eager 이벤트가 발생하면 이 구성요소 카테고리 변경 기능에 액세스할 수 없습니다. 사용자가 준비되기 전에 선택 상자를 열고 항목을 탐색하기만 하면 이벤트가 실행되기 때문입니다.

더 나은 <select> 변경 일정

const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])

// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
  let ignoreChange = false

  nav.addEventListener('change', e => {
    if (ignoreChange) return
    // it's actually changed!
  })

  nav.addEventListener('keydown', ({ key }) => {
    if (preventedKeys.has(key))
      ignoreChange = true
    else if (allowedKeys.has(key))
      ignoreChange = false
  })
})

이를 위한 전략은 각 <select> 요소에서 키보드 다운 이벤트를 감시하고 눌린 키가 탐색 확인 (Tab 또는 Enter)인지 공간 탐색 (ArrowUp 또는 ArrowDown)인지 결정하는 것입니다. 이 결정으로 구성요소는 <select> 요소 이벤트가 실행될 때 대기하거나 이동할지 결정할 수 있습니다.

결론

이제 제가 어떻게 했는지 알았으니 어떻게 되세요?‽ 🙂

접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. 데모를 만들고 링크를 트윗해 주세요. 그러면 아래의 커뮤니티 리믹스 섹션에 추가하겠습니다.

커뮤니티 리믹스