Uma visão geral básica de como criar um sidenav deslizante responsivo
Nesta postagem, quero compartilhar como criei um protótipo de um componente Sidenav para a Web que é responsivo, tem estado, oferece suporte à navegação por teclado, funciona com e sem JavaScript e funciona em todos os navegadores. Teste a demonstração.
Se preferir vídeos, confira a 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 vão ter computadores poderosos e outros vão acessar o site em um dispositivo móvel pequeno. Todas as pessoas que visitarem o site devem poder abrir e fechar o menu.
Web Tactics
Nesta análise de componentes, tive a alegria de combinar alguns recursos essenciais da plataforma da Web:
- CSS
:target
- grade 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 de UX do
focus
Minha solução tem uma barra lateral e alterna apenas quando está em uma janela de visualização "mobile" de 540px
ou menos.
540px
será o 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 do hash do URL da página. Em seguida, com uma pseudoclasse, mostro e oculto o sidenav:
@media (max-width: 540px) {
#sidenav-open {
visibility: hidden;
}
#sidenav-open:target {
visibility: visible;
}
}
Grade CSS
No passado, eu só usava layouts e componentes de navegação lateral
em posição absoluta ou fixa. No entanto, 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,
uma de cada uma é 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, 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;
}
}
Plano de fundo do menu
O <aside>
é o elemento de animação que contém a navegação lateral. Ela tem
duas filhas: o contêiner de navegação <nav>
com o nome [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 que você quer para a sobreposição de menu e o botão de fechar do espaço negativo.
Transformações e transições 3D do CSS
Agora nosso layout está empilhado no tamanho de uma janela de visualização para dispositivos móveis. Até que eu adicione alguns estilos novos, ele vai sobrepor nosso artigo por padrão. Aqui estão alguns UX que eu quero alcançar nesta próxima seção:
- Animar a abertura e o fechamento
- Só animar com movimento se o usuário permitir
- 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 começar com a acessibilidade em mente.
Movimento acessível
Nem todo mundo quer uma experiência de movimento deslizante. 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 sistema operacional de um usuário para movimento (se disponível).
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
Agora, quando o sidenav desliza para abrir e fechar, se um usuário preferir um movimento reduzido, eu moverei o elemento para a visualização instantaneamente, mantendo o estado sem movimento.
Transição, transformação, tradução
Sidenav para fora (padrão)
Para definir o estado padrão do menu lateral em dispositivos móveis como um estado fora da tela,
posiciono o elemento com transform: translateX(-110vw)
.
Adicionei outro 10vw
ao código típico fora da tela de -100vw
,
para garantir que o box-shadow
do menu lateral não apareça na viewport 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
,
e observe como o CSS desliza o elemento da posição de -110vw
para a posição de 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 da tela,
para que os sistemas não coloquem o foco em um menu fora da tela. Para fazer isso, configure uma
transição de visibilidade quando o :target
mudar.
- Ao entrar, não faça a transição de visibilidade. Seja visível imediatamente para que eu possa ver o elemento deslizar e aceitar o foco.
- Ao sair, transfira a visibilidade, mas a atrase, para que ela mude para
hidden
no final da transição.
Aprimoramentos na UX de acessibilidade
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 recebe alguns recursos de acessibilidade
sem custo financeiro. Vamos decorar nossos elementos interativos com rótulos que expressem 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 indicam claramente a intenção para mouse e teclado.
:is(:hover, :focus)
Esse pseudo-seletor funcional CSS útil nos permite ser rapidamente inclusivos com nossos estilos de passar o cursor, compartilhando-os também com o foco.
.hamburger:is(:hover, :focus) svg > line {
stroke: hsl(var(--brandHSL));
}
JavaScript básico
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 abertura e fechamento adicione várias entradas ao histórico do navegador, adicione o seguinte JavaScript inline ao botão de fechamento:
<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, fazendo com que pareça que o menu nunca foi aberto.
Focus UX
O snippet a seguir nos ajuda a colocar o foco nos botões de abrir e fechar depois de abrir ou fechar. 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 o sidenav abrir, foque o botão de fechar. Quando o sidenav fechar,
foque o botão aberto. Para isso, chamo focus()
no elemento em JavaScript.
Conclusão
Agora que você sabe como eu fiz, 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, envie um tweet com sua versão e 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 Glitch Remix: demonstração e código
- @EvroMalarkey com HTML/CSS/JS: demonstração e código