Como criar um componente de navegação estrutural

Uma visão geral básica de como criar um componente de navegação estrutural responsivo e acessível para que os usuários naveguem pelo site.

Nesta postagem, quero compartilhar ideias sobre uma maneira de criar componentes da navegação estrutural. Teste a demonstração.

Demonstração

Se você preferir o vídeo, aqui está uma versão do YouTube desta postagem:

Visão geral

Um componente de localização atual mostra onde o usuário está na hierarquia do site. O nome é de Hansel and Gretel, que fez o download migalhas de pão atrás deles em uma floresta escura e conseguiam encontrar seu caminho para casa traçando migalhas de trás para frente.

A navegação estrutural desta postagem não é padrão navegação estrutural, semelhantes a uma navegação estrutural. Elas oferecem mais funcionalidades ao colocar páginas diretamente na navegação com um <select>, tornando o acesso a várias camadas sempre que possível.

UX em segundo plano

No vídeo de demonstração do componente acima, as categorias de espaço reservado são gêneros de video games. Essa trilha é criada pelo seguinte caminho: home » rpg » indie » on sale, conforme mostrado abaixo.

Esse componente de navegação estrutural deve permitir que os usuários naveguem por esse hierarquia de informações pulando galhos e selecionando páginas com velocidade precisão.

Arquitetura de informações

Acho útil pensar em termos de coleções e itens.

Coleções

Uma coleção é uma matriz de opções para você escolher. Da página inicial do no protótipo de navegação estrutural desta postagem, as coleções são FPS, RPG, brawler, rastreador de masmorras, esportes e quebra-cabeças.

Itens

Um videogame é um item, uma coleção específica também pode ser um item representa outra coleção. Por exemplo, RPG é um item e um coleção. Quando é um item, o usuário acessa a página da coleção. Por exemplo: ele está na página RPG, que exibe uma lista de jogos de RPG, incluindo subcategorias adicionais AAA, Indie e Autopublicação.

Em termos de ciência da computação, esse componente de navegação estrutural representa uma multidimensional matriz:

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

Seu app ou site vai ter uma arquitetura da informação (AI) personalizada, criando uma uma matriz multidimensional diferente, mas espero que o conceito de destino da coleta páginas e a travessia de hierarquia também podem fazê-lo em sua navegação estrutural.

Layouts

Marcação

Bons componentes começam com o HTML apropriado. Na próxima seção, vou abordar as opções de marcação e como elas afetam o componente geral.

Esquema claro e escuro

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

A metatag color-scheme no exemplo acima snippet informa ao navegador que esta página quer a versão clara e a escura estilos. As localizações atuais do exemplo não incluem CSS para esses esquemas de cores, Assim, a navegação estrutural usará as cores padrão fornecidas pelo navegador.

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

É apropriado usar o Elemento <nav> para navegação no site, que tem uma função ARIA implícita de navegação. Nos testes, percebi que ter o atributo role mudou a forma como um leitor de tela interagiu com o elemento, ele foi, na verdade, anunciado como e decidi adicioná-la.

Ícones

Quando um ícone é repetido em uma página, o SVG Elemento <use> significa que é possível definir o path uma vez e usá-lo em todas as instâncias do ícone. Isso evita que as mesmas informações de caminho sejam repetidas, documentos maiores e o potencial de inconsistência de caminho.

Para usar essa técnica, adicione um elemento SVG oculto à página e envolva os ícones. em um elemento <symbol> com um ID exclusivo:

<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>

O navegador lê o HTML SVG, armazena as informações do ícone na memória e continua com o resto da página fazendo referência ao ID para outros usos de o ícone, da seguinte forma:

<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>

DevTools mostrando um elemento de uso SVG renderizado.

Defina uma vez e use quantas vezes quiser, com impacto mínimo no desempenho da página e estilização flexível. Observe que aria-hidden="true" foi adicionado ao elemento SVG. Os ícones não são úteis para alguém que apenas ouve o conteúdo ao navegar, ocultando e impede que adicionem ruído desnecessário.

É aqui que a navegação estrutural tradicional e as que estão neste componente divergem. Normalmente, seria apenas um link <a>, mas adicionei a UX de apresentação com uma seleção disfarçada. A classe .crumb é responsável por dispor o link e do ícone, enquanto .crumbicon é responsável por empilhar o ícone e selecionar elemento reunido. Chamei de link de divisão porque suas funções são muito semelhante a um botão de divisão, mas também para a navegação nas páginas.

<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>

Um link e algumas opções não são nada de especial, mas acrescentam mais funcionalidade a uma navegação estrutural simples. Adicionar um title ao elemento <select> é útil para a tela. usuários do leitor, fornecendo informações sobre a ação do botão. No entanto, também ajuda todos os outros, você vai perceber que está em primeiro lugar iPad. Um atributo fornece contexto de botão para muitos usuários.

Captura de tela com o elemento de seleção invisível passando o cursor sobre ele e o
dica contextual exibida.

Decorações do separador

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

Os separadores são opcionais, mas adicionar apenas um funciona muito bem (confira o terceiro exemplo no vídeo acima). Depois, dou aria-hidden="true" a cada um, já que eles são decorativos e não algo que um leitor de tela precisa anunciar.

A propriedade gap, abordada a seguir, torna o espaçamento simples.

Estilos

Como a cor usa cores do sistema, são principalmente lacunas e pilhas para estilos.

Direção e fluxo do layout

DevTools mostrando alinhamento de navegação da navegação estrutural com uma sobreposição flexbox
.

O elemento de navegação principal nav.breadcrumbs define uma propriedade personalizada com escopo para uso dos filhos e estabelece um ambiente horizontal alinhado o mesmo layout organizacional. Isso garante que as pistas, os divisores e os ícones se alinhem.

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

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

Uma navegação estrutural mostrada verticalmente com sobreposições de flexbox.

Cada .crumb também estabelece um layout horizontal alinhado verticalmente com algumas gap, mas segmenta especialmente seus filhos do link e especifica o estilo white-space: nowrap. Isso é crucial para a navegação estrutural com várias palavras, usar várias linhas. Mais adiante nesta postagem, adicionaremos estilos para lidar com as estouro horizontal causado pela propriedade 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" foi adicionado para ajudar a destacar o link da página atual dos descansar. Os usuários de leitores de tela não apenas terão uma indicação clara de que o link é para a página atual, ajustamos o estilo visual do elemento para ajudar usuários com visão normal ter uma experiência do usuário semelhante.

O componente .crumbicon usa a grade para empilhar um ícone SVG com um invisível" <select>.

Grid DevTools mostrada sobre um botão em que a linha e a coluna estão
pilha de nomes.

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

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

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

O elemento <select> é o último no DOM e fica no topo da pilha. e interativos. Adicione um estilo de opacity: .01 para que o elemento ainda possa ser usado. e o resultado é uma caixa de seleção que se encaixa perfeitamente no formato do ícone. Essa é uma boa maneira de personalizar a aparência de um elemento <select> enquanto e manter a funcionalidade integrada.

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

Menu flutuante

Navegação estrutural deve ser capaz de representar um caminho muito longo. Gosto de permitir as coisas saírem da tela horizontalmente, quando apropriado, e senti o componente de navegação estrutural foi bem qualificado.

.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;
  }}
}

Os estilos de sobrecarga configuram a UX a seguir:

  • Rolagem horizontal com contenção de rolagem.
  • Padding de rolagem horizontal.
  • Um ponto de ajuste na última trilha. Isso significa que, no carregamento da página, carregamentos de navegação no local e visíveis.
  • Remove o ponto de ajuste do Safari, que tem dificuldade com a horizontal combinações de rolagem e efeito de ajuste.

Consultas de mídia

Um ajuste sutil para janelas de visualização menores é ocultar a página inicial o rótulo, deixando apenas o ícone:

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

Lado a lado das localizações atuais com e sem um marcador de página inicial, por
comparação.

Acessibilidade

Movimento

Não há muito movimento nesse componente, mas ao quebrar o em uma verificação de prefers-reduced-motion, podemos impedir movimentos indesejados.

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

Nenhum dos outros estilos precisa mudar, os efeitos de passar o cursor e de foco são ótimos e significativa sem uma transition, mas se não houver problema em movimento, adicionaremos uma para a interação.

JavaScript

Primeiro, independentemente do tipo de roteador que você usa em seu site ou aplicativo, quando um usuário altera a navegação estrutural, o URL precisa ser atualizado, e o usuário a página adequada. Em segundo lugar, para normalizar a experiência do usuário, verifique se nenhuma navegação inesperada acontece quando os usuários estão apenas navegando pelo <select> .

Duas medidas críticas de experiência do usuário que devem ser tratadas pelo JavaScript: select has prevenção de disparo de eventos de alteração <select> rápida e alterada.

A prevenção rápida de eventos é necessária devido ao uso de um <select>. . No Windows Edge e em outros navegadores, selecione changed. é disparado à medida que o usuário navega pelas opções com o teclado. É por isso que eu chamado de ansioso, já que o usuário apenas pseudoselecionou a opção, como passar o cursor ou foco, mas ainda não tiver confirmado a escolha com enter ou click. O ansioso torna esse recurso de alteração de categoria do componente inacessível, porque abrir a caixa de seleção e procurar um item acionará o evento e mudar a página, antes que o usuário esteja pronto.

Um evento <select> melhor mudou

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
  })
})

A estratégia para isso é observar os eventos de pressionamento do teclado em cada <select>. e determinar se a tecla pressionada foi uma confirmação de navegação (Tab ou Enter) ou a navegação espacial (ArrowUp ou ArrowDown). Com este determinada, o componente pode decidir aguardar ou continuar, quando o evento do O elemento <select> é acionado.

Conclusão

Agora que você sabe como eu fiz isso, o que você faria‽ 🙂

Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie uma demonstração, envie um tweet para mim e adicione os links acesse a seção "Remixes da comunidade" abaixo.

Remixes da comunidade