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.
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.
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;
}
Á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);
}
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;
}
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:
- Clique com o botão direito do mouse na página e selecione Inspecionar elemento para abrir o DevTools.
- Clique na engrenagem de configurações no canto superior direito da janela do DevTools.
- No título Elements, encontre e ative a opção Mostrar sombra do user agent DOM.
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);
}
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);
}
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:
- O atributo
value
do elemento<progress>
. - O atributo
aria-valuenow
. - 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()
}
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.