Como criar um componente de rolagem de mídia

Uma visão geral básica de como criar uma visualização de rolagem horizontal responsiva para TVs, smartphones, computadores etc.

Nesta postagem, quero compartilhar ideias sobre como criar experiências de rolagem horizontal para a Web que sejam mínimas, responsivas, acessíveis e funcionem em vários navegadores e plataformas (como TVs!). Teste a demonstração.

Demonstração

Se preferir vídeo, confira uma 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 ajustando-as a uma grade. O JavaScript é adicionado para facilitar interações de índice móvel, ajudando os usuários de teclado a pular a navegação por 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 leve de rolador de título.

Comece com uma marcação acessível

Um scroller de mídia é feito de apenas alguns componentes principais, uma lista com itens. Uma lista, na sua forma mais simples, pode viajar pelo mundo todo 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 ver um item. Essa é nossa base acessível.

Forneça uma lista com um elemento <ul>:

<ul class="horizontal-media-scroller">
  <li></li>
  <li></li>
  <li></li>
  ...
<ul>

Deixe 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 carrossel de mídia é uma oportunidade de UX para ajudar a trazer mais contexto à miniatura ou como texto de substituição se a imagem não for carregada. Ele também fornece uma interface falada para usuários que dependem de tecnologia assistiva, 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 origem de imagem só deve ser buscada quando a imagem estiver na janela de visualização. Isso é muito útil para listas grandes, já que os usuários só baixam imagens dos itens que rolaram para a visualização.

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 user agent fornecidos no modo claro e escuro. É um modo escuro ou claro sem custo financeiro, dependendo do seu 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 por tema escuro. Isso significa que as navegações entre páginas do site não vão mostrar um fundo branco entre os carregamentos. Tema escuro perfeito entre carregamentos, muito mais agradável aos olhos.

Saiba muito mais com Thomas Steiner em https://web.dev/color-scheme/.

Adicionar conteúdo

Considerando a estrutura de conteúdo acima de ul > li > a > figure > picture > img, a próxima tarefa é adicionar imagens e títulos para rolar. A demonstração tem imagens e texto estáticos de marcador de posição, mas você pode usar sua fonte de dados favorita.

Adicionar estilo com CSS

Agora é a vez do CSS pegar essa lista genérica de conteúdo e transformá-la em uma experiência. A Netflix, as lojas de apps e muitos outros sites e apps usam áreas de rolagem horizontal para preencher a janela de visualização com categorias e opções.

Como criar o layout do scroller

É importante evitar cortar o conteúdo nos layouts ou usar a truncagem de texto com reticências. Muitas TVs têm roladores de mídia como este, mas com muita frequência recorrem à elipse de conteúdo. Este layout não! Ele também permite que o conteúdo de mídia substitua o tamanho da coluna, tornando um layout flexível o suficiente para lidar com muitas combinações interessantes.

2
linhas de rolagem mostradas. Um não tem reticências, o que significa que ele é mais alto e cada
título é totalmente legível. O outro é mais curto, e muitos títulos são cortados com
reticências.

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. Ele gerencia apenas espaçamento e 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 nossa proporção de tela básica: 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 mais alguns estilos menores, conclua a estrutura básica do scroller 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 pelo teclado na lista. Em seguida, cada elemento filho direto <li> tem o ::marker removido ao receber um novo tipo de exibição inline-block.

As imagens ainda não são responsivas e saem das caixas em que estão. Domine-os com alguns tamanhos, ajustes e estilos de borda, além de um gradiente de plano de fundo para quando estiverem com carregamento lento:

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%)
    );
}

Padding de rolagem

O alinhamento ao conteúdo da página e uma área de superfície de rolagem de ponta a ponta são essenciais para um componente harmonioso e minimalista.

Para criar o layout de rolagem de ponta a ponta 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 padding de rolagem horizontal O exemplo acima mostra como é fácil adicionar padding a um contêiner de rolagem, mas há problemas de compatibilidade pendentes (corrigidos no Chromium 91 ou mais recente). Confira aqui um pouco da história, mas a versão curta é que o padding nem sempre era considerado em uma visualização de rolagem.

Uma caixa é destacada no lado inline-end do último item da lista, mostrando que o padding e o elemento têm a mesma largura para criar o alinhamento desejado.

Para enganar os navegadores e fazer com que eles coloquem o padding no final da barra de rolagem, vou segmentar 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 scroller de mídia funcione em qualquer modo de escrita e direção do documento.

Ajuste de rolagem

Um contêiner de rolagem com estouro pode se tornar uma janela de visualização de ajuste com uma linha de CSS. Depois, cabe aos filhos especificar como eles querem se alinhar a essa janela.

.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 grande popularidade dele em TVs, app stores e muito mais. Muitas plataformas de videogame usam um scroller de mídia muito parecido com este como layout principal da tela inicial. O foco é um grande momento de UX, não apenas uma pequena adição. Imagine usar esse scroller de mídia do sofá com um controle remoto. Faça algumas pequenas melhorias nessa interação:

.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, a ela um bom espaço. Se o usuário não tiver preferências de movimento para reduzir o movimento, o deslocamento será transicionado, um movimento sutil ao evento de foco.

Índice móvel

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 isso é chamado de índice móvel. Isso acontece quando um contêiner de itens está em foco pelo teclado, mas apenas um filho pode manter o foco por vez. Essa experiência de um item focalizável por vez foi projetada para permitir ignorar a 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 fazê-los percorrer 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 facilitar essa experiência do usuário. Veja como usar para os três roladores:

import {rovingIndex} from 'roving-ux';

rovingIndex({
  element: someElement
});

Esta demonstração consulta o documento para os roladores e, para cada um deles, chama a função rovingIndex(). Transmita o rovingIndex() do elemento para ter a experiência de foco móvel, como um contêiner de lista, e um seletor de consulta de destino, caso os destinos 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 post foi escrito, o suporte para aspect-ratio está atrás de uma flag no Firefox, mas disponível em navegadores Chromium ou set-top boxes. Como o layout de 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 a compatibilidade com proporção. Melhoria progressiva em alguns scrollers de mídia mais dinâmicos.

Uma caixa com proporção de 4:4 é mostrada ao lado das outras proporções de design usadas de 16:9 e 4:3.

@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 for compatível com a sintaxe aspect-ratio, as imagens do scroller de mídia serão atualizadas para o dimensionamento aspect-ratio. Usando a sintaxe de aninhamento de rascunho, cada imagem muda a proporção de acordo com a primeira, segunda ou terceira linha. A sintaxe de aninhamento também permite definir alguns pequenos ajustes de viewport, ali mesmo com a outra lógica de dimensionamento.

Com esse CSS, à medida que o recurso estiver disponível em mais mecanismos de navegador, um layout fácil de gerenciar, mas mais atraente visualmente, será renderizado.

Prefere dados reduzidos

Embora essa próxima técnica esteja disponível apenas atrás de uma flag no Canary, 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 algum estado de dados reduzidos, como um modo de economia de dados. Se for, posso modificar o documento e, nesse caso, ocultar as imagens.

ALT_TEXT_HERE

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 do download de imagens pesadas. Este é o site antes de adicionar o CSS prefers-reduced-data:

(7 solicitações, 100 KB de recursos em 131 ms)

ALT_TEXT_HERE

Confira o desempenho do site depois de adicionar o CSS prefers-reduced-data:

ALT_TEXT_HERE

(71 solicitações, 1,2 MB de recursos em 1,07 s)

64 solicitações a menos, que seriam as ~60 imagens na janela de visualização (testes feitos em uma tela widescreen) desta guia do navegador, um aumento de ~80% no carregamento da página e 10% dos dados na rede. CSS bem poderoso.

Conclusão

Agora que você sabe como eu fiz isso, 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, me envie um tweet com ela e eu vou adicionar à seção de remixes da comunidade abaixo.

Origem

Remixes da comunidade

Ainda não há nada disponível.