Uma visão geral básica sobre como criar uma visualização de rolagem horizontal responsiva para TVs, smartphones, computadores etc.
Nesta postagem, quero compartilhar ideias sobre maneiras de criar experiências de rolagem horizontal para a Web que sejam mínimas, responsivas, acessíveis e funcionem em navegadores e plataformas (como TVs). Teste a demonstração.
Se você preferir o vídeo, aqui está uma versão do YouTube desta postagem:
Visão geral
Vamos criar um layout de rolagem horizontal para hospedar miniaturas de
mídia ou produtos. O componente começa como uma lista <ul>
simples, mas é
transformado com CSS em uma experiência de rolagem satisfatória e suave, mostrando
imagens e encaixando-as em uma grade. O JavaScript foi adicionado para facilitar
as interações de índice móvel, ajudando os usuários de teclado a pular a travessia de mais de 100 itens.
Além disso, uma consulta de mídia experimental, prefers-reduced-data
, é usada para transformar o
rolador de mídia em uma experiência de rolagem de título leve.
Comece com marcação acessível
Um scroller de mídia é composto por apenas alguns componentes principais, uma lista com itens. Uma lista, na forma mais simples, pode viajar pelo mundo e ser claramente consumida por todos. Um usuário que acessa essa página pode navegar por uma lista e clicar em um link para conferir um item. Essa é a nossa base acessível.
Envie uma lista com um elemento <ul>
:
<ul class="horizontal-media-scroller">
<li></li>
<li></li>
<li></li>
...
<ul>
Torne os itens da lista interativos com um elemento <a>
:
<li>
<a href="#">
...
</a>
</li>
Use um elemento <figure>
para representar semanticamente uma imagem e sua legenda:
<figure>
<picture>
<img alt="..." loading="lazy" src="https://picsum.photos/500/500?1">
</picture>
<figcaption>Legends</figcaption>
</figure>
Observe os atributos alt
e loading
no <img>
. O texto alternativo para um scroller
de mídia é uma oportunidade de UX para ajudar a trazer mais contexto à miniatura, ou como
texto alternativo se a imagem não carregar, ou fornece uma interface falada para usuários
que dependem de tecnologia adaptativa, como um leitor de tela. Saiba mais com Cinco regras
de ouro para texto alternativo
em conformidade.
O atributo loading
aceita a palavra-chave lazy
como uma maneira de sinalizar que essa fonte
de imagem só precisa ser buscada quando a imagem estiver dentro da viewport. Isso pode ser
muito bom para listas grandes, já que os usuários só vão fazer o download de imagens de itens que
rolaram para visualizar.
Oferecer suporte à preferência de esquema de cores do usuário
Use color-scheme
como uma tag <meta>
para sinalizar ao navegador que sua página
quer os estilos de agente do usuário claro e escuro. É um modo escuro
ou claro sem custo financeiro, dependendo do ponto de vista:
<meta name="color-scheme" content="dark light">
A metatag fornece o sinal mais rápido possível para que o navegador possa selecionar uma cor de tela escura padrão se o usuário tiver uma preferência por tema escuro. Isso significa que as navegações entre as páginas do site não vão mostrar uma tela branca como plano de fundo entre os carregamentos. Tema escuro perfeito entre as cargas, muito mais agradável para os olhos.
Saiba muito mais com Thomas Steiner em https://web.dev/color-scheme/.
Adicionar conteúdo
Dada a estrutura de conteúdo acima de ul > li > a > figure > picture > img
,
a próxima tarefa é adicionar imagens e títulos para rolar. Inclua imagens e texto de marcadores de posição na demonstração, mas fique à vontade para potencializar isso na sua fonte de dados favorita.
Adicionar estilo com CSS
Agora é hora de o CSS transformar essa lista genérica de conteúdo em uma experiência. Netflix, app stores e muitos outros sites e apps usam áreas de rolagem horizontal para preencher a janela de visualização com categorias e opções.
Criar o layout do botão de rolagem
É importante evitar cortar o conteúdo em layouts ou usar truncamento de texto com reticências. Muitos aparelhos de TV têm roladores de mídia como esse, mas, com muita frequência, recorrem a conteúdo elíptico. Este layout não tem. Ele também permite que o conteúdo de mídia substitua o tamanho da coluna, tornando um layout flexível o suficiente para processar muitas combinações interessantes.
O contêiner permite substituir o tamanho da coluna fornecendo o tamanho padrão como uma propriedade personalizada. Esse layout de grade tem uma opinião sobre o tamanho da coluna, gerenciando apenas o espaçamento e a direção:
.horizontal-media-scroller {
--size: 150px;
display: grid;
grid-auto-flow: column;
gap: calc(var(--gap) / 2); /* parent owned value for children to be relative to*/
margin: 0;
}
A propriedade personalizada é usada pelo elemento <picture>
para criar a proporção de base: uma caixa:
.horizontal-media-scroller {
--size: 150px;
display: grid;
grid-auto-flow: column;
gap: calc(var(--gap) / 2);
margin: 0;
& picture {
inline-size: var(--size);
block-size: var(--size);
}
}
Com apenas alguns estilos menores, conclua o básico do controle deslizante de mídia:
.horizontal-media-scroller {
--size: 150px;
display: grid;
grid-auto-flow: column;
gap: calc(var(--gap) / 2);
margin: 0;
overflow-x: auto;
overscroll-behavior-inline: contain;
& > li {
display: inline-block; /* removes the list-item bullet */
}
& picture {
inline-size: var(--size);
block-size: var(--size);
}
}
A configuração overflow
configura o <ul>
para permitir a rolagem e a navegação por teclado
na lista. Em seguida, cada elemento <li>
filho direto tem o ::marker
removido
ao receber um novo tipo de exibição de inline-block
.
No entanto, as imagens ainda não são responsivas e saem das caixas em que estão. Controle-as com alguns tamanhos, ajustes e estilos de borda, além de um gradiente de plano de fundo para quando elas forem carregadas de forma lenta:
img {
/* smash into whatever box it's in */
inline-size: 100%;
block-size: 100%;
/* don't squish but do cover the space */
object-fit: cover;
/* soften the edges */
border-radius: 1ex;
overflow: hidden;
/* if empty, show a gradient placeholder */
background-image:
linear-gradient(
to bottom,
hsl(0 0% 40%),
hsl(0 0% 20%)
);
}
Espaçamento de rolagem
O alinhamento ao conteúdo da página, além de uma área de rolagem de ponta a ponta, são essenciais para um componente harmonioso e mínimo.
Para criar o layout de rolagem de borda a borda que se alinha com nossa tipografia
e linhas de layout, use padding
que corresponda ao scroll-padding
:
.horizontal-media-scroller {
--size: 150px;
display: grid;
grid-auto-flow: column;
gap: calc(var(--gap) / 2);
margin: 0;
overflow-x: auto;
overscroll-behavior-inline: contain;
padding-inline: var(--gap);
scroll-padding-inline: var(--gap);
padding-block: calc(var(--gap) / 2); /* make space for scrollbar and focus outline */
}
Correção de bug de preenchimento de rolagem horizontal O exemplo acima mostra como é fácil preencher um contêiner de rolagem, mas há problemas de compatibilidade pendentes (corrigidos no Chromium 91+). Confira aqui um pouco do histórico, mas a versão resumida é que o padding nem sempre foi contabilizado em uma visualização de rolagem.
Para enganar os navegadores e colocar o padding no final do scroller, vou direcionar a última figura em cada lista e anexar um pseudoelemento que seja a quantidade de padding desejada.
.horizontal-media-scroller > li:last-of-type figure {
position: relative;
&::after {
content: "";
position: absolute;
inline-size: var(--gap);
block-size: 100%;
inset-block-start: 0;
inset-inline-end: calc(var(--gap) * -1);
}
}
O uso de propriedades lógicas permite que o controle de mídia funcione em qualquer modo de gravação e direção do documento.
Ajuste de rolagem
Um contêiner de rolagem com overflow pode se tornar uma viewport de ajuste com uma linha de CSS. Em seguida, as crianças precisam especificar como elas querem se alinhar com essa viewport.
.horizontal-media-scroller {
--size: 150px;
display: grid;
grid-auto-flow: column;
gap: calc(var(--gap) / 2);
margin: 0;
overflow-x: auto;
overscroll-behavior-inline: contain;
padding-inline: var(--gap);
scroll-padding-inline: var(--gap);
padding-block-end: calc(var(--gap) / 2);
scroll-snap-type: inline mandatory;
& figure {
scroll-snap-align: start;
}
}
Foco
A inspiração para esse componente vem da enorme popularidade em TVs, em app stores e muito mais. Muitas plataformas de videogame usam um scroller de mídia muito semelhante a este como layout principal da tela inicial. O foco é um momento de UX muito importante, não apenas uma pequena adição. Imagine usar esse botão de rolagem de mídia no seu sofá com um controle remoto e dar a essa interação algumas pequenas melhorias:
.horizontal-media-scroller a {
outline-offset: 12px;
&:focus {
outline-offset: 7px;
}
@media (prefers-reduced-motion: no-preference) {
& {
transition: outline-offset .25s ease;
}
}
}
Isso define o estilo de contorno de foco 7px
longe da caixa, dando a ele um bom
espaço. Se o usuário não tiver preferências de movimento para reduzir o movimento, o deslocamento
será transferido, dando um movimento sutil ao evento de foco.
Índice itinerante
Os usuários de gamepad e teclado precisam de atenção especial nessas longas listas de conteúdo e opções de rolagem. O padrão comum para resolver esse problema é chamado de índice itinerante. É quando um contêiner de itens é focado no teclado, mas apenas um filho pode manter o foco por vez. Essa experiência de item único com foco foi projetada para permitir a omissão da lista potencialmente longa de itens, em vez de pressionar a tecla Tab mais de 50 vezes para chegar ao fim.
Há 300 itens no primeiro scroller da demonstração. Podemos fazer melhor do que fazê-las passar por todos eles para chegar à próxima seção.
Para criar essa experiência, o JavaScript precisa observar eventos de teclado e de foco. Criei uma pequena biblioteca de código aberto no npm para ajudar a tornar essa experiência do usuário fácil de alcançar. Confira como usá-lo para os três controles de rolagem:
import {rovingIndex} from 'roving-ux';
rovingIndex({
element: someElement
});
Essa demonstração consulta o documento para os roladores e, para cada um deles, chama a
função rovingIndex()
. Transmita o rovingIndex()
ao elemento para ter a experiência
móvel, como um contêiner de lista e um seletor de consulta de destino, caso os
alvos de foco não sejam descendentes diretos.
document.querySelectorAll('.horizontal-media-scroller')
.forEach(scroller =>
rovingIndex({
element: scroller,
target: 'a',
}))
Para saber mais sobre esse efeito, consulte a biblioteca de código aberto roving-ux.
Proporção
No momento em que este artigo foi escrito, o suporte a
aspect-ratio
estava atrás de uma
flag no Firefox, mas estava disponível nos navegadores Chromium ou nas caixas de seleção. Como o
layout da grade do scroller de mídia especifica apenas a direção e o espaçamento, o dimensionamento pode
mudar dentro de uma consulta de mídia, que verifica o suporte à proporção.
Aprimoramento progressivo em alguns controles de rolagem de mídia mais dinâmicos.
@supports (aspect-ratio: 1) {
.horizontal-media-scroller figure > picture {
inline-size: auto; /* for a block-size driven ratio */
aspect-ratio: 1; /* boxes by default */
@nest section:nth-child(2) & {
aspect-ratio: 16/9;
}
@nest section:nth-child(3) & {
/* double the size of the others */
block-size: calc(var(--size) * 2);
aspect-ratio: 4/3;
/* adjust size to fit more items into the viewport */
@media (width <= 480px) {
block-size: calc(var(--size) * 1.5);
}
}
}
}
Se o navegador oferecer suporte à sintaxe aspect-ratio
, as imagens do controle deslizante de mídia serão
atualizadas para o dimensionamento aspect-ratio
. Usando a sintaxe de aninhamento de rascunho, cada imagem
muda a proporção dependendo se ela está na primeira, segunda ou terceira linha. A
sintaxe de aninhamento também permite definir alguns pequenos
ajustes de janela de visualização, com a outra lógica de dimensionamento.
Com esse CSS, como o recurso está disponível em mais mecanismos de navegador, um layout fácil de gerenciar, mas visualmente mais atraente, será renderizado.
Prefere dados reduzidos
Embora essa próxima técnica esteja disponível apenas
através de uma flag no
Canary,
eu queria compartilhar como consegui economizar uma quantidade considerável de tempo de carregamento da página e
uso de dados com algumas linhas de CSS. A consulta de mídia prefers-reduced-data
do
nível 5 permite perguntar se o dispositivo está em
estados de dados reduzidos, como um modo de economia de dados. Se estiver, posso modificar o documento
e, neste caso, ocultar as imagens.
figure {
@media (prefers-reduced-data: reduce) {
& {
min-inline-size: var(--size);
& > picture {
display: none;
}
}
}
}
O conteúdo ainda pode ser navegado, mas sem o custo das imagens pesadas sendo
transferidas por download. Confira o site antes de adicionar o CSS prefers-reduced-data
:
(7 solicitações, 100 KB de recursos em 131 ms)
Confira a performance do site após a adição do CSS prefers-reduced-data
:
(71 solicitações, 1,2 MB de recursos em 1,07 s)
64 solicitações a menos, que seriam as cerca de 60 imagens na viewport (testes feitos em uma tela widescreen) dessa guia do navegador, um aumento de carregamento de página de cerca de 80% e 10% dos dados pela rede. CSS bastante poderoso.
Conclusão
Agora que você sabe como eu fiz, como você faria? 🙂
Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie um Codepen ou hospede sua própria demonstração, envie um tweet para mim e vou adicionar à seção "Remixes da comunidade" abaixo.
Origem
Remixes da comunidade
Ainda não há nada aqui.