Uma visão geral básica de como criar uma navegação lateral deslizante responsiva
Nesta postagem, quero compartilhar com vocês como fiz o protótipo de um componente de navegação lateral para a Web que é responsivo, com estado, oferece suporte à navegação pelo teclado, funciona com e sem JavaScript, e funciona em vários navegadores. Confira a demonstração.
Se você preferir o vídeo, aqui está uma versão do YouTube desta postagem:
Visão geral
É difícil criar um sistema de navegação responsivo. Alguns usuários vão usar um teclado, alguns terão computadores potentes e outros usarão de um pequeno dispositivo móvel. Todos os visitantes devem conseguir abrir e fechar o menu.
Táticas da Web
Nesta análise detalhada de componentes, tive a satisfação de combinar alguns recursos essenciais da plataforma da Web:
- CSS
:target
- Grade do CSS
- Transformações CSS
- Consultas de mídia CSS para a janela de visualização e preferência do usuário
- JS para melhorias de UX do
focus
Minha solução tem uma barra lateral e alterna somente quando em um dispositivo móvel janela de visualização de 540px
ou menos.
O 540px
será nosso ponto de interrupção para alternar entre o layout interativo para dispositivos móveis e o layout estático para computadores.
Pseudoclasse CSS :target
Um link <a>
define o hash de URL como #sidenav-open
e o outro como vazio (''
).
Por fim, um elemento tem o id
para corresponder ao hash:
<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>
Clicar em cada um desses links altera o estado de hash do URL da nossa página, depois, com uma pseudoclasse, mostro e escolho a navegação lateral:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
Grade CSS
Antes, eu só usava a posição absoluta ou fixa
layouts e componentes de navegação lateral. No entanto, a grade, com a sintaxe grid-area
,
nos permite atribuir vários elementos à mesma linha ou coluna.
Pilhas
O elemento de layout principal #sidenav-container
é uma grade que cria uma linha e duas colunas,
Um de cada um é chamado de stack
. Quando o espaço é restrito, o CSS atribui todos os atributos<main>
filhos no mesmo nome de grade, colocando todos os elementos no mesmo espaço, criando uma pilha.
#sidenav-container {
display: grid;
grid: [stack] 1fr / min-content [stack] 1fr;
min-height: 100vh;
}
@media (max-width: 540px) {
#sidenav-container > * {
grid-area: stack;
}
}
Pano de fundo do menu
O <aside>
é o elemento de animação que contém a navegação lateral. Ele tem
Dois filhos: o contêiner de navegação <nav>
chamado [nav]
e um pano de fundo <a>
.
chamado [escape]
, que é usado para fechar o menu.
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
Ajuste 2fr
e 1fr
para encontrar a proporção desejada para a sobreposição do menu e o botão "Fechar" do espaço negativo.
Transformações CSS 3D e transições
Agora, nosso layout está empilhado no tamanho da janela de visualização de dispositivos móveis. Até adicionar alguns estilos novos, ela se sobrepõe ao nosso artigo por padrão. Aqui está uma UX que estou buscando na próxima seção:
- Animar para abrir e fechar
- A animação só precisa ser feita se o usuário concordar com o movimento
- Animar
visibility
para que o foco do teclado não entre no elemento fora da tela
Conforme começo a implementar animações de movimento, quero começar pensando na acessibilidade.
Movimento acessível
Nem todo mundo vai querer uma experiência de movimento de deslizar para fora. Na nossa solução, essa preferência
é aplicada ajustando uma variável CSS --duration
dentro de uma consulta de mídia. Esse valor de consulta de mídia representa
a preferência do usuário pelo sistema operacional em relação ao movimento (se disponível).
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Agora, quando a navegação lateral deslizar aberta e fechada, se um usuário preferir o movimento reduzido, Eu movo o elemento para visualização na hora, mantendo o estado sem movimento.
Transição, transformação e tradução
Saída da navegação lateral (padrão)
Para definir o estado padrão de nossa navegação lateral em dispositivos móveis para um estado fora da tela,
Posiciono o elemento com transform: translateX(-110vw)
.
Adicionei outro 10vw
ao código fora da tela típico de -100vw
,
para garantir que o box-shadow
da navegação lateral não apareça na janela de visualização principal quando ela estiver oculta.
@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);
}
}
Navegação lateral em
Quando o elemento #sidenav
corresponder a :target
, defina a posição translateX()
como a base inicial 0
.
e observe enquanto o CSS desliza o elemento de -110vw
para dentro
posição de 0
em var(--duration)
quando o hash de URL é alterado.
@media (max-width: 540px) {
#sidenav-open:target {
visibility: visible;
transform: translateX(0);
transition:
transform var(--duration) var(--easeOutExpo);
}
}
Visibilidade da transição
O objetivo agora é ocultar o menu dos leitores de tela quando ele estiver indisponível,
para que os sistemas não coloquem foco em um menu fora da tela. Eu faço isso definindo uma
transição de visibilidade quando o :target
muda.
- Ao entrar, não mude a visibilidade. visível de imediato para que eu possa ver o elemento deslizar e aceitar o foco.
- Ao sair, faça a transição de visibilidade, mas atrase para que ela mude para
hidden
no final da transição.
Melhorias de acessibilidade na UX
Links
Essa solução depende da alteração do URL para que o estado seja gerenciado.
Naturalmente, o elemento <a>
precisa ser usado aqui, e ele recebe uma boa acessibilidade
sem custo financeiro. Vamos enfeitar nossos elementos interativos com rótulos que expressam claramente a intenção.
<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>
Agora, nossos botões de interação principais declaram claramente a intenção deles para mouse e teclado.
:is(:hover, :focus)
Esse pseudoseletor funcional de CSS prático permite ser rapidamente inclusivos com nossos estilos de passar o cursor, compartilhando-os também com foco.
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
Borrifar no JavaScript
Pressione escape
para fechar
A tecla Escape
do teclado vai fechar o menu corretamente? Vamos conectar isso.
const sidenav = document.querySelector('#sidenav-open');
sidenav.addEventListener('keyup', event => {
if (event.code === 'Escape') document.location.hash = '';
});
Histórico do navegador
Para evitar que a interação de abertura e fechamento empilhe vários entradas no histórico do navegador, adicione o seguinte JavaScript inline o botão Fechar:
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>
Isso removerá a entrada do histórico de URLs no fechamento, fazendo com que o menu fosse nunca abriu.
Foco na UX
O próximo snippet nos ajuda a colocar o foco nos botões de abrir e fechar depois eles abrem ou fecham. Quero facilitar a alternância.
sidenav.addEventListener('transitionend', e => {
const isOpen = document.location.hash === '#sidenav-open';
isOpen
? document.querySelector('#sidenav-close').focus()
: document.querySelector('#sidenav-button').focus();
})
Quando a navegação lateral abrir, foque o botão "Fechar". Quando a navegação lateral é fechada,
focar no botão "Abrir". Para fazer isso, chame focus()
no elemento em JavaScript.
Conclusão
Agora que você sabe como eu fiz isso, como faria?! Isso deixa uma arquitetura de componentes divertida! Quem vai criar a primeira versão com slots? 🙂
Vamos diversificar e aprender todas as maneiras de criar na Web. Crie um Glitch, Envie um tweet para sua versão, e eu a adicionarei ao seção Remixes da comunidade abaixo.
Remixes da comunidade
- @_developit com elementos personalizados: demonstração e código
- @mayeedwin1 com HTML/CSS/JS: demonstração e código
- @a_nurella com um remix do Glitch: demo & código
- @EvroMalarkey com HTML/CSS/JS: demonstração e código