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 suas 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.

Demonstração

Se preferir vídeo, aqui está uma versão do YouTube desta postagem:

Visão geral

Vamos criar um layout de rolagem horizontal destinado a hospedar miniaturas de mídia ou produtos. O componente começa como uma modesta lista de <ul>, mas é transformado com CSS em uma experiência de rolagem satisfatória e suave, mostrando imagens e ajustando-as a uma grade. O JavaScript foi adicionado para facilitar as interações do índice de deslocamento, ajudando os usuários do teclado a pular mais de 100 itens. Além disso, uma consulta de mídia experimental, prefers-reduced-data, é usada para transformar o controle de rolagem de mídia em uma experiência leve de rolagem de título.

Comece com uma marcação acessível

Um botão de rolagem de mídia é composto por apenas alguns dos principais componentes: uma lista com itens. Uma lista, em sua forma mais simples, pode viajar o mundo todo e ser claramente consumida por todos. Um usuário que acessa esta página pode navegar em uma lista e clicar em um link para visualizar um item. Esta é 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 botão de rolagem de mídia é uma oportunidade de UX para ajudar a trazer o contexto extra da miniatura ou como texto substituto se a imagem não for carregada ou fornecer uma interface falada para usuários que dependem de tecnologia adaptativa, como um leitor de tela. Saiba mais em Cinco regras de ouro para textos alternativos compatíveis.

O atributo loading aceita a palavra-chave lazy como uma forma de sinalizar que a origem dessa imagem precisa ser buscada somente quando a imagem estiver dentro da janela de visualização. Isso pode ser muito bom para listas grandes, já que os usuários só vão fazer o download de imagens para itens que eles rolaram para ver.

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 claro e escuro fornecidos. Trata-se de um modo escuro ou claro sem custo financeiro, dependendo de como você o analisa:

<meta name="color-scheme" content="dark light">

A metatag fornece o primeiro sinal 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 páginas do site não vão mostrar um plano de fundo de tela branca entre os carregamentos. Tema escuro integrado entre os carregamentos, muito mais agradável para os olhos.

Saiba mais com Thomas Steiner (em inglês) em https://web.dev/color-scheme/ (em inglês).

Adicionar conteúdo

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

Adicionar estilo com CSS

Agora é hora do CSS pegar essa lista genérica de conteúdo e transformá-la 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.

Como criar o layout de rolagem

É importante evitar cortar o conteúdo nos layouts ou usar o truncamento do texto com reticências. Muitos televisores têm controles de rolagem de mídia como este, mas muitas vezes recorrem a conteúdo com reticências. Esse layout não faz isso. 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 exibidas. Um não tem reticências, o que significa que é mais alto e os títulos são totalmente legíveis. O outro é mais curto, e muitos títulos são cortados
por reticências.

O contêiner permite substituir o tamanho da coluna, fornecendo o tamanho padrão como uma propriedade personalizada. Esse layout de grade é opinativo sobre o tamanho da coluna e gerencia 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 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 secundários, complete a parte básica do botão de rolagem 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);
  }
}

Definir overflow configura o <ul> para permitir rolagem e navegação pelo teclado pela lista. Cada elemento filho <li> direto terá 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 diretamente das caixas em que estão. Domine-os com alguns tamanhos, ajustes e estilos de borda, além de um gradiente de segundo plano 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%)
    );
}

Rolar o padding

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

Para realizar o layout de rolagem de ponta a ponta alinhado às nossas linhas de tipografia e layout, use padding que corresponda a 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 no padding de rolagem horizontal As informações acima mostram como seria fácil preencher um contêiner de rolagem, mas há problemas de compatibilidade pendentes com ele, mas foram corrigidos no Chromium 91 e versões mais recentes. Confira aqui um pouco do histórico, mas a versão resumida é que o padding nem sempre foi considerado em uma visualização de rolagem.

Uma
caixa é destacada no lado in-line do último item da lista, mostrando que o
padding e o elemento têm a mesma largura que o alinhamento pretendido.

Para fazer com que os navegadores coloquem o padding no final do botão de rolagem, vamos direcionar a última figura em cada lista e anexar um pseudoelemento que é 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 botão de rolagem de mídia funcione em qualquer modo de gravação e direção de 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, os filhos especificam como eles gostariam de se alinhar a essa janela de visualização.

.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 de sua enorme popularidade em TVs, na App Store e muito mais. Muitas plataformas de videogame usam um botão de rolagem de mídia muito semelhante a esse, como o layout principal da tela inicial. Aqui, o foco é um grande momento de UX, não apenas um pequeno acréscimo. Imagine usar esse botão de rolagem no sofá com um controle remoto e fazer 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 do contorno do foco 7px para longe da caixa, proporcionando um bom espaço. Se o usuário não tiver preferências de movimento para a redução, o deslocamento vai ser transferido, fornecendo movimentos sutis 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 isso é chamado de índice rotativo. É quando um contêiner de itens é focado no teclado, mas apenas um filho pode manter o foco por vez. Essa experiência de item focalizável único por vez foi projetada para permitir ignorar uma lista possivelmente longa de itens, em vez de pressionar mais de 50 vezes a tecla Tab para chegar ao fim.

Há 300 itens nesse primeiro botão de rolagem da demonstração. Podemos fazer melhor do que fazê-los atravessar todos eles para chegar à próxima seção.

Para criar essa experiência, o JavaScript precisa observar eventos de teclado e eventos de foco. Criei uma pequena biblioteca de código aberto no npm para facilitar essa experiência do usuário. Confira como usá-lo com os três controles de rolagem:

import {rovingIndex} from 'roving-ux';

rovingIndex({
  element: someElement
});

Esta demonstração consulta o documento em busca dos controles de rolagem e, para cada um deles, chama a função rovingIndex(). Transmita o elemento 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 esta postagem foi escrita, a compatibilidade com aspect-ratio (link em inglês) atrás de uma sinalização no Firefox, mas disponível em navegadores Chromium e conversores. Como o layout da grade de rolagem de mídia especifica apenas a direção e o espaçamento, o dimensionamento pode mudar dentro de uma consulta de mídia em que o recurso verifica a compatibilidade com a proporção. Aprimoramento progressivo em alguns controles de rolagem 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 oferecer suporte à sintaxe aspect-ratio, as imagens de rolagem de mídia vão ser atualizadas para o dimensionamento aspect-ratio. Usando a sintaxe de aninhamento de rascunho, a proporção de cada imagem muda de acordo com a primeira, segunda ou terceira linhas. A sintaxe Nest também permite definir alguns pequenos ajustes da janela de visualização, com a outra lógica de dimensionamento.

Com esse CSS, à medida que 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

Essa próxima técnica está disponível apenas por trás de uma flag no Canary (link em inglês), mas quero mostrar como economizar uma quantidade considerável de tempo de carregamento de 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.

ALT_TEXT_HERE

figure {
  @media (prefers-reduced-data: reduce) {
    & {
      min-inline-size: var(--size);

      & > picture {
        display: none;
      }
    }
  }
}

O conteúdo ainda é navegável, 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)

ALT_TEXT_HERE

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

ALT_TEXT_HERE

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

64 solicitações a menos, aproximadamente 60 imagens dentro da janela de visualização (testes realizados em uma tela widescreen) dessa guia do navegador, um aumento de carregamento da página de cerca de 80% e 10% dos dados pela rede. CSS muito eficiente.

Conclusão

Agora que você sabe como eu fiz isso, como você iria?! 🙂

Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie um Codepen ou apresente sua própria demonstração, envie um tweet para mim e vou adicioná-la à seção "Remixes da comunidade" abaixo.

Origem

Remixes da comunidade

Ainda não temos nada aqui.