Como criar um componente da barra de carregamento

Uma visão geral básica de como criar uma barra de carregamento adaptável e acessível com o elemento <progress>.

Nesta postagem, quero compartilhar ideias sobre como criar uma cor adaptável e barra de carregamento acessível com o elemento <progress>. Experimente o demonstração e acesse a fonte.

Luz e escura, indeterminada, crescente e conclusão demonstradas no Chrome.

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

Visão geral

A <progress> fornece feedback visual e audível para os usuários sobre a conclusão. Isso o feedback visual é valioso para cenários como: progresso em um formulário, exibir informações de download ou upload, ou mesmo mostrando que o o valor do progresso é desconhecido, mas o trabalho ainda está ativo.

Este desafio da GUI trabalhou com o elemento HTML <progress> existente para economizar algum esforço de acessibilidade. A cores e layouts ampliam os limites de personalização do elemento integrado, para modernizar o componente e fazer com que ele se encaixe melhor nos sistemas de design.

guias claras e escuras em cada navegador, fornecendo uma 
    Visão geral do ícone adaptativo de cima para baixo: 
    Safari, Firefox e Chrome.
Demonstração para Firefox, Safari, iOS Safari Chrome e Android Chrome em esquemas claros e escuros.

Marcação

Decidi envolver o elemento <progress> em uma <label> portanto É possível ignorar os atributos de relacionamento explícitos em favor de um valor implícito relacionamento. Também rotulei um elemento pai afetado pelo estado de carregamento, então a tela as tecnologias de leitor podem retransmitir essas informações para o usuário.

<progress></progress>

Se não houver value, o progresso do elemento será indeterminado. O padrão do atributo max é 1, portanto, o progresso está entre 0 e 1. Configurando max como 100, por exemplo, definiria o intervalo como 0-100. Decidi ficar dentro de 0 e 1, o que traduz os valores de progresso em 0,5 ou 50%.

Progresso com wrapper de rótulo

Em uma relação implícita, um elemento de progresso é unido por um rótulo como este:

<label>Loading progress<progress></progress></label>

Na demonstração, optei por incluir o marcador para leitores de tela apenas. Isso é feito unindo o texto do rótulo em uma <span> e aplicando alguns estilos para que fique efetivamente fora da tela:

<label>
  <span class="sr-only">Loading progress</span>
  <progress></progress>
</label>

Com o seguinte CSS complementar do WebAIM:

.sr-only {
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
}

Captura de tela do DevTools revelando o elemento somente pronto para a tela.

Área afetada pelo progresso de carregamento

Se você tem uma visão saudável, pode ser fácil associar um indicador de progresso com elementos relacionados e áreas de página, mas, para usuários com deficiência visual, isso não é tão claro. Melhore isso atribuindo aria-busy atributo para o elemento mais acima, que será alterado quando o carregamento for concluído. Além disso, indique uma relação entre o progresso e a zona de carregamento com aria-describedby

<main id="loading-zone" aria-busy="true">
  …
  <progress aria-describedby="loading-zone"></progress>
</main>

No JavaScript, alterne aria-busy para true no início da tarefa e para false após a conclusão.

Adições de atributos Aria

Embora o papel implícito de um elemento <progress> seja progressbar, o texto foi explícito para navegadores que não têm esse papel implícito. Também adicionei o atributo indeterminate para colocar explicitamente o elemento em um estado desconhecido, que é mais clara do que observar que o elemento não tem value definido

<label>
  Loading 
  <progress 
    indeterminate 
    role="progressbar" 
    aria-describedby="loading-zone"
    tabindex="-1"
  >unknown</progress>
</label>

Usar tabindex="-1" para tornar o elemento de progresso focalizável a partir do JavaScript. Isso é importante para do leitor de tela, já que dar foco ao progresso à medida que ele muda, anunciará ao usuário o quanto o progresso atualizado foi alcançado.

Estilos

O elemento de progresso é um pouco complicado quando se trata de estilo. HTML integrado os elementos têm partes escondidas especiais que podem ser difíceis de selecionar e muitas vezes oferecem apenas um conjunto limitado de propriedades a serem definidas.

Layout

Os estilos de layout visam permitir alguma flexibilidade no progresso o tamanho do elemento e a posição do rótulo. É adicionado um estado de conclusão especial que pode ser uma indicação visual útil, mas não obrigatória.

Layout de <progress>

A largura do elemento de progresso não é alterada para que possa diminuir e aumentar com o espaço necessário no design. Os estilos integrados são eliminados definindo appearance e border como none. Isso é feito para que o elemento possa ser normalizados em navegadores, já que cada um tem estilos próprios .

progress {
  --_track-size: min(10px, 1ex);
  --_radius: 1e3px;

  /*  reset  */
  appearance: none;
  border: none;

  position: relative;
  height: var(--_track-size);
  border-radius: var(--_radius);
  overflow: hidden;
}

O valor de 1e3px para _radius usa número científico notável para expressar uma número grande, então border-radius seja sempre arredondado. É equivalente a 1000px: Gosto de usá-lo porque meu objetivo é usar um valor grande o suficiente Posso definir e esquecer. O limite é mais curto que 1000px. Também é se necessário, basta mudar o 3 para 4, e 1e4px ficará equivalente a 10000px.

overflow: hidden é usado e tem sido um estilo polêmico. Conseguiu alguns coisas fáceis, como não precisar transmitir valores border-radius para o rastrear e rastrear elementos de preenchimento; mas também não foi filho do progresso poderiam ficar fora do elemento. Outra iteração do progresso personalizado elemento pode ser feito sem overflow: hidden e pode abrir alguns oportunidades para animações ou melhores estados de conclusão.

Progresso concluído

Os seletores de CSS fazem o trabalho difícil aqui comparando o valor máximo com o valor. Se eles corresponderem, o progresso estará concluído. Quando concluído, um pseudoelemento é gerado e anexado ao final do elemento "progress", oferecendo uma boa indicação visual adicional para a conclusão.

progress:not([max])[value="1"]::before,
progress[max="100"][value="100"]::before {
  content: "";
  
  position: absolute;
  inset-block: 0;
  inset-inline: auto 0;
  display: flex;
  align-items: center;
  padding-inline-end: max(calc(var(--_track-size) / 4), 3px);

  color: white;
  font-size: calc(var(--_track-size) / 1.25);
}

Captura de tela da barra de carregamento em 100% mostrando uma marca de seleção no final.

Cor

O navegador traz as próprias cores para o elemento de progresso e se adapta claro e escuro com apenas uma propriedade CSS. Isso pode ser baseado em algumas seletores especiais específicos do navegador.

Estilos de navegador claro e escuro

Para incluir seu site em um elemento <progress> adaptável claro e escuro, faça o seguinte: color-scheme é tudo o que é necessário.

progress {
  color-scheme: light dark;
}

Cor preenchida do progresso de uma propriedade

Para a tonalidade de um elemento <progress>, use accent-color.

progress {
  accent-color: rebeccapurple;
}

Observe que a cor de fundo da faixa muda de claro para escuro, dependendo accent-color: O navegador está garantindo o contraste adequado: muito bonito.

Cores claras e escuras totalmente personalizadas

Defina duas propriedades personalizadas no elemento <progress>, uma para a cor da faixa. e outro para a cor do progresso da faixa. Dentro do prefers-color-scheme consulta de mídia, forneça novos valores de cor para a faixa e acompanhe o progresso.

progress {
  --_track: hsl(228 100% 90%);
  --_progress: hsl(228 100% 50%);
}

@media (prefers-color-scheme: dark) {
  progress {
    --_track: hsl(228 20% 30%);
    --_progress: hsl(228 100% 75%);
  }
}

Estilos de foco

Anteriormente, fornecemos ao elemento um índice de guias negativas para que ele pudesse ser usado de forma programática focada. Usar :focus-visible para personalize o foco para usar o estilo de anel de foco inteligente. Com isso, um mouse o clique e o foco não mostram o anel de foco, mas os cliques do teclado sim. A Um vídeo do YouTube detalha isso e que vale a pena revisar.

progress:focus-visible {
  outline-color: var(--_progress);
  outline-offset: 5px;
}

Captura de tela da barra de carregamento com um anel de foco ao redor. Todas as cores são correspondentes.

Estilos personalizados em vários navegadores

Personalize os estilos selecionando as partes de um elemento <progress> que cada navegador expõe. O elemento "progress" é usado com uma única tag, mas com uma alguns elementos filhos expostos por pseudosseletores de CSS. Chrome DevTools mostrará esses elementos se você ativar a configuração:

  1. Clique com o botão direito do mouse na página e selecione Inspecionar elemento para abrir o DevTools.
  2. Clique na engrenagem de configurações no canto superior direito da janela do DevTools.
  3. No título Elements, encontre e ative a opção Mostrar sombra do user agent DOM.

Captura de tela do local no DevTools para ativar a exposição do shadow DOM do user agent.

Estilos do Safari e do Chromium

Navegadores baseados em WebKit, como Safari e Chromium, expõem ::-webkit-progress-bar e ::-webkit-progress-value, que permitem um subconjunto de CSS a ser usado. Por enquanto, defina background-color usando as propriedades personalizadas. criados anteriormente, que se adaptam ao claro e ao escuro.

/*  Safari/Chromium  */
progress[value]::-webkit-progress-bar {
  background-color: var(--_track);
}

progress[value]::-webkit-progress-value {
  background-color: var(--_progress);
}

Captura de tela mostrando os elementos internos do elemento de progresso.

Estilos do Firefox

O Firefox só expõe o pseudoseletor ::-moz-progress-bar na <progress>. Isso também significa que não podemos colorir a faixa diretamente.

/*  Firefox  */
progress[value]::-moz-progress-bar {
  background-color: var(--_progress);
}

Captura de tela do Firefox e onde encontrar as partes do elemento &quot;progress&quot;.

Captura de tela do espaço de depuração, onde o Safari, o iOS Safari 
  O Firefox, o Chrome e o Chrome no Android mostram a barra de carregamento funcionando.

Observe que o Firefox tem uma cor de trilha definida em accent-color, enquanto o Safari do iOS tem uma faixa azul claro. O mesmo acontece no modo escuro: o Firefox tem uma faixa escura, mas não a cor personalizada que definimos, e funciona em navegadores baseados em Webkit.

Animação

Ao trabalhar com pseudosseletores integrados no navegador, geralmente há uma limitação de propriedades CSS permitidas.

Animar o preenchimento da faixa

Adicionar uma transição ao inline-size de o elemento progress funciona no Chromium, mas não no Safari. O Firefox também faz não use uma propriedade de transição na ::-moz-progress-bar.

/*  Chromium Only 😢  */
progress[value]::-webkit-progress-value {
  background-color: var(--_progress);
  transition: inline-size .25s ease-out;
}

Como animar o estado :indeterminate

Aqui eu uso um pouco mais de criatividade para criar uma animação. Um pseudoelemento do Chromium é criado e um gradiente é aplicado de volta animado e para os três navegadores.

As propriedades personalizadas

As propriedades personalizadas são ótimas para muitas coisas, mas uma das minhas favoritas é simplesmente e dar um nome a um valor CSS de aparência mágica. Seguir um exemplo complexo linear-gradient, mas com um nome legal. Sua finalidade e casos de uso podem ser claramente compreendidos.

progress {
  --_indeterminate-track: linear-gradient(to right,
    var(--_track) 45%,
    var(--_progress) 0%,
    var(--_progress) 55%,
    var(--_track) 0%
  );
  --_indeterminate-track-size: 225% 100%;
  --_indeterminate-track-animation: progress-loading 2s infinite ease;
}

As propriedades personalizadas também vão ajudar o código a continuar DRY, já que não podemos agrupe esses seletores específicos do navegador.

Os frames-chave

O objetivo é uma animação infinita que vai para frente e para trás. O início e o fim frames-chave serão definidos no CSS. Só é necessário um frame-chave, o do meio em 50%, para criar uma animação que retorne ao ponto de onde ela começou, várias vezes repetidamente!

@keyframes progress-loading {
  50% {
    background-position: left; 
  }
}

Segmentação de cada navegador

Nem todos os navegadores permitem a criação de pseudoelementos na <progress> próprio elemento ou permite animar a barra de progresso. Mais navegadores compatíveis a animação da faixa em vez de um pseudoelemento, então eu atualizo os pseudoelementos em uma base e em barras animadas.

Pseudoelemento do Chromium

O Chromium permite que o pseudoelemento ::after seja usado com uma posição para cobrir o elemento. As propriedades personalizadas indeterminadas são usadas, e as propriedades de retorno e funciona muito bem.

progress:indeterminate::after {
  content: "";
  inset: 0;
  position: absolute;
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}
Barra de progresso do Safari

No Safari, as propriedades personalizadas e uma animação são aplicadas à barra de progresso do pseudoelemento:

progress:indeterminate::-webkit-progress-bar {
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}
Barra de progresso do Firefox

No Firefox, as propriedades personalizadas e uma animação também são aplicadas à barra de progresso do pseudoelemento:

progress:indeterminate::-moz-progress-bar {
  background: var(--_indeterminate-track);
  background-size: var(--_indeterminate-track-size);
  background-position: right; 
  animation: var(--_indeterminate-track-animation);
}

JavaScript

O JavaScript tem um papel importante com o elemento <progress>. Ele controla o valor enviado ao elemento e garante a presença de informações suficientes no documento para leitores de tela.

const state = {
  val: null
}

A demonstração tem botões para controlar o progresso. eles atualizam state.val e chamar uma função para atualizar DOM.

document.querySelector('#complete').addEventListener('click', e => {
  state.val = 1
  setProgress()
})

setProgress()

É nessa função que ocorre a orquestração da UI/UX. Comece criando um função setProgress(). Nenhum parâmetro é necessário porque ele tem acesso ao Objeto state, elemento de progresso e zona <main>.

const setProgress = () => {
  
}

Como definir o status de carregamento na zona <main>

Dependendo de o progresso estar concluído ou não, o <main> relacionado precisa de uma atualização aria-busy :

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)
}

Limpar atributos se o valor de carregamento for desconhecido

Se o valor for desconhecido ou não definido, null neste uso, remova value e aria-valuenow. Isso fará com que a <progress> se torne indeterminada.

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }
}

Corrigir problemas matemáticos decimais do JavaScript

Como optei por manter o padrão máximo de progresso de 1, a demonstração as funções de incremento e decremento usam matemática decimal. JavaScript e outros idiomas, nem sempre são muito boas em isso. Confira uma função roundDecimals() que vai cortar o excesso do cálculo. resultado:

const roundDecimals = (val, places) =>
  +(Math.round(val + "e+" + places)  + "e-" + places)

Arredonde o valor para que ele possa ser apresentado e legível:

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"
}

Definir o valor dos leitores de tela e do estado do navegador

O valor é usado em três locais no DOM:

  1. O atributo value do elemento <progress>.
  2. O atributo aria-valuenow.
  3. O conteúdo de texto interno <progress>.
const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"

  progress.value = val
  progress.setAttribute('aria-valuenow', valPercent)
  progress.innerText = valPercent
}

Dar foco ao progresso

Com os valores atualizados, os usuários com visão normal verão a mudança de progresso, mas a tela os usuários de leitores ainda não serão informados da mudança. Concentre-se no <progress>, e o navegador anunciará a atualização.

const setProgress = () => {
  zone.setAttribute('aria-busy', state.val < 1)

  if (state.val === null) {
    progress.removeAttribute('aria-valuenow')
    progress.removeAttribute('value')
    progress.focus()
    return
  }

  const val = roundDecimals(state.val, 2)
  const valPercent = val * 100 + "%"

  progress.value = val
  progress.setAttribute('aria-valuenow', valPercent)
  progress.innerText = valPercent

  progress.focus()
}

Captura de tela do app de voz do Mac OS 
  lendo o progresso da barra de carregamento para o usuário.

Conclusão

Agora que você sabe como eu fiz isso, o que você faria‽ 🙂

Há algumas mudanças que eu gostaria de fazer se tivesse outra chance. Acho que há espaço para limpar o componente atual e para tentar criar um sem as limitações de estilo da pseudoclasse do elemento <progress>. Vale a pena explorar!

Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web.

Crie uma demonstração, envie um tweet para mim e adicione os links acesse a seção "Remixes da comunidade" abaixo.

Remixes da comunidade