Uma visão geral básica de como criar uma Scrollview horizontal responsiva para TVs, smartphones, computadores etc.
Neste post, quero compartilhar ideias sobre como 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 preferir vídeos, confira a versão desta postagem no YouTube:
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 a legenda dela:
<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 dar mais contexto à miniatura ou como
texto alternativo se a imagem não carregar, ou fornece uma interface falada para usuários
que dependem de tecnologias adaptativas, 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 padrão escura se o usuário tiver uma preferência de tema escuro. Isso significa que as navegações entre as páginas do site não vão mostrar um plano de fundo de tela branca 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. Eu preparei a demonstração com
imagens e texto de marcador de posição estáticos, mas sinta-se à vontade para usar 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. A Netflix, as app stores e muitos outros sites e apps usam áreas de rolagem horizontal para agrupar a viewport com categorias e opções.
Como criar o layout do controle deslizante
É 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 todos recorrem com frequência 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, é essencial 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 era contabilizado em uma visualização de rolagem.
Para enganar os navegadores e colocar o padding no final do scroller, vou direcionar a última figura de 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 controle de mídia no sofá com um controle remoto. Dê 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 afasta o estilo de contorno de foco 7px
da caixa, proporcionando um visual agradável
espaço. Se o usuário não tiver preferências de movimento para reduzir o movimento, o deslocamento
será transferido, um movimento sutil ao evento de foco.
Índice itinerante
Os usuários de gamepad e teclado precisam de atenção especial nessas listas longas 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 pelo 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 final.
Há 300 itens no primeiro scroller da demonstração. Podemos fazer melhor do que fazer eles atravessarem todos 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 receber 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 direção e espaçamento, o dimensionamento pode
mudar dentro de uma consulta de mídia, que verifica o suporte à proporção.
Melhoria progressiva 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 viewport, 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 mais visualmente 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 for, 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 que estão sendo
transferidas. 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.