Uma visão geral básica de como criar uma sidenav responsiva que desliza para fora
Nesta postagem, quero compartilhar com você como criei um protótipo de um componente Sidenav para a Web que é responsivo, com estado, compatível com navegação por teclado, funciona com e sem JavaScript e em todos os navegadores. Teste a demonstração.
Se preferir vídeo, confira uma versão desta postagem no YouTube:
Visão geral
É difícil criar um sistema de navegação responsivo. Alguns usuários vão usar um teclado, outros terão computadores potentes e outros vão acessar de um pequeno dispositivo móvel. Todos os visitantes precisam conseguir abrir e fechar o menu.
Táticas da Web
Nesta exploração de componentes, tive a alegria de combinar alguns recursos essenciais da plataforma da Web:
- CSS
:target
- grid do CSS
- Transformações do CSS
- Consultas de mídia CSS para janela de visualização e preferência do usuário
- JS para melhorias na UX do
focus
Minha solução tem uma barra lateral e só alterna quando está em uma janela de visualização "móvel" de 540px
ou menos.
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 :target
do CSS
Um link <a>
define o hash do 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 muda o estado hash do URL da página. Em seguida, com uma pseudoclasse, mostro e oculto a barra de navegação lateral:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
Grade CSS
Antes, eu só usava layouts e componentes de barra lateral de posição absoluta ou fixa. A grade, com a sintaxe grid-area
,
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, sendo uma de cada chamada stack
. Quando o espaço é limitado, o CSS atribui todos os filhos do elemento <main>
ao mesmo nome de grade, colocando todos os elementos no mesmo espaço e 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;
}
}
Tela de fundo do menu
O <aside>
é o elemento de animação que contém a navegação lateral. Ela tem dois filhos: o contêiner de navegação <nav>
chamado [nav]
e um plano 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 de fechar do espaço negativo.
Transformações e transições 3D do CSS
Nosso layout agora está empilhado em um tamanho de janela de visualização para dispositivos móveis. Até que eu adicione alguns novos estilos, ele vai sobrepor nosso artigo por padrão. Confira algumas experiências do usuário que quero criar na próxima seção:
- Animar abrir e fechar
- Só use animação com movimento se o usuário concordar com isso
- Animar
visibility
para que o foco do teclado não entre no elemento fora da tela
Ao começar a implementar animações de movimento, quero priorizar a 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 de um usuário pelo sistema operacional para movimento (se disponível).
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Agora, quando a barra lateral está abrindo e fechando, se um usuário preferir movimento reduzido, eu movo instantaneamente o elemento para a visualização, mantendo o estado sem movimento.
Transição, transformação, tradução
Sidenav out (padrão)
Para definir o estado padrão da barra de navegação lateral em dispositivos móveis como 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 barra de navegação lateral não apareça na janela de visualização principal quando estiver oculto.
@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
Quando o elemento #sidenav
corresponder a :target
, defina a posição translateX()
como 0
da base inicial e observe como o CSS desliza o elemento da posição externa -110vw
para a posição "interna" 0
em var(--duration)
quando o hash do 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 fora,
para que os sistemas não foquem em um menu fora da tela. Para isso, defino uma transição de visibilidade quando o :target
muda.
- Ao entrar, não faça a transição da visibilidade. Fique visível imediatamente para que eu possa ver o elemento deslizar e aceitar o foco.
- Ao sair, faça a transição da visibilidade, mas atrase-a para que ela mude para
hidden
no final da transição de saída.
Melhorias na UX de acessibilidade
Links
Essa solução depende da mudança do URL para que o estado seja gerenciado.
Naturalmente, o elemento <a>
deve ser usado aqui, e ele recebe alguns recursos de acessibilidade
interessantes sem custos financeiros. Vamos adornar nossos elementos interativos com rótulos que articulam 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, os botões de interação principais indicam claramente a intenção para mouse e teclado.
:is(:hover, :focus)
Esse pseudoseletor funcional do CSS permite que sejamos inclusivos rapidamente com nossos estilos de passar o cursor, compartilhando-os também com o foco.
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
Adicionar JavaScript
Pressione escape
para fechar
A tecla Escape
no teclado fecha o menu, certo? 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 abrir e fechar acumule várias entradas no histórico do navegador, adicione o seguinte JavaScript inline ao botão de fechar:
<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>
Isso remove a entrada do histórico de URL ao fechar, como se o menu nunca tivesse sido aberto.
Foco na UX
O snippet a seguir ajuda a focar nos botões de abrir e fechar depois que eles são abertos ou fechados. 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 de fechar. Quando a navegação lateral for fechada, foque o botão de abrir. Para isso, chamo focus()
no elemento em JavaScript.
Conclusão
Agora que você sabe como eu fiz isso, como você faria? Isso cria uma arquitetura de componentes divertida. Quem vai criar a primeira versão com slots? 🙂
Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie um Glitch, me envie um tweet com sua versão, e eu vou adicioná-la à 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: demonstração e código
- @EvroMalarkey com HTML/CSS/JS: demonstração e código