o protótipo
De qualquer forma, estou codificando um elemento personalizado “Dots” . Pontos são aqueles pequenos botões complicados usados para navegar em carrosséis e coisas assim. Sim, eu sei que carrosséis são ruins e protesto, mas nem sempre a decisão é minha.
Veja este GIF:
Obviamente, um monte de pontos são inúteis por si só. Pretendo combiná-los com um componente carrossel que criarei a seguir. Isso já não é um problema resolvido? Tenho pesquisado muitos carrosséis, todos implementados de maneira diferente. Meu objetivo é construir um conjunto de elementos combináveis que possam formar um carrossel ou ser usados em outro lugar. Onde mais os “Pontos” podem ser usados? Não sei, pare de fazer perguntas difíceis.
Estilizando
Este é o aspecto menos importante do componente. Adicionei CSS suficiente para a demonstração ficar bonita. A única coisa que vale a pena notar é o pseudoelemento oculto que estou usando para expandir a área de destino. Os pontos são 50% maiores do que parecem e, portanto, mais fáceis de cutucar. O efeito alongado é opcional e facilmente removido. Não tenho certeza se gosto, mas está na moda e é uma demonstração visual mais interessante.
Marcação acessível
Achei que seria um elemento personalizado simples de codificar, mas é surpreendentemente complicado. Qual é a marcação correta? Eu poderia usar uma lista de botões:
<dots-component> <ol> <li> <button type="button" aria-selected="true"> <span>Item 1</span> </button> </li> <li> <button type="button"> <span>Item 2</span> </button> </li> </ol></dots-component>
Muitos exemplos que pesquisei usam uma lista. A maioria gosta do uso do Splide<ul>
, mas certamente <ol>
o uso do Flickity está correto? Independentemente disso, optei por ficar sem:
<dots-component> <button type="button" aria-selected="true"> <span>Item 1</span> </button> <button type="button"> <span>Item 2</span> </button></dots-component>
Os leitores de tela podem anunciar os números da lista, o que parece redundante e confuso com os rótulos dos botões. Estou adicionando role
atributos posteriormente, o que também afeta isso.
Eu ponderei se <button>
estava correto ou se deveria permitir <a href="#item-1">
um aprimoramento mais progressivo. Como (ainda) não estou construindo o carrossel inteiro, não posso presumir que os links de fragmentos foram implementados corretamente.
O carrossel do Bootstrap renuncia a qualquer elemento interativo e espera que o usuário clique em uma altura de três pixels <li>
– ah, querido. O carrossel Glide não está listado com <button>
elementos que não possuem rótulo. Nenhum deles afirma ser acessível e o Bootstrap chega a dizer que “os carrosséis geralmente não atendem aos padrões de acessibilidade” . Pelo menos eles são honestos.
Para rótulos de botões, considerei usar aria-label
em vez do arquivo oculto span
. O exemplo do W3C adota essa abordagem. No entanto, o W3C também diz que a primeira regra do ARIA é que vocênão fale, quero dizer, vamos apenas citar o documento:
Se você puder usar um elemento ou atributo HTML nativo com a semântica e o comportamento necessários já integrados, em vez de redirecionar um elemento e adicionar uma função, estado ou propriedade ARIA para torná-lo acessível, faça-o.
Resumindo: “Se você pode usar [HTML nativo], faça-o” . Então eu fiz isso. Chris Ferdinandi escreveu sobre outros problemas usando o aria-label, então estou evitando isso.
Padrões como o carrossel do Filament Group são usados role="tablist"
na embalagem role="tab"
dos botões. Adicionei essas funções porque é uma prática que parece incontestada. O VoiceOver da Apple anuncia o número de guias durante a navegação.
Usar aria-selected
o botão ativo é o atributo aceito para guias. Inicialmente pensei aria-current
mas isso é mais apropriado para paginação. Claro, eu poderia usar uma classe HTML para estilizar, seguindo a regra ARIA 1, mas esse atributo adiciona contexto e estilo [aria-selected="true"]
é um bônus.
A marcação final fica assim:
<dots-component role="tablist"> <button type="button" aria-selected="true" tabindex="0" role="tab"> <span>Item 1</span> </button> <button type="button" aria-selected="false" tabindex="-1" role="tab"> <span>Item 2</span> </button></dots-component>
O elemento personalizado JavaScript adiciona e atualiza os atributos conforme necessário (notas tabindex
abaixo). O que você acha? Estou procurando feedback.
API interativa
Estou mais confiante na questão da interatividade. O JavaScript é bastante robusto e pode lidar com modificações no DOM se botões forem adicionados, removidos, etc. Os seletores de consulta não são armazenados em cache (otimização excessiva). Um único click
ouvinte de evento é registrado para lidar com a navegação do ponteiro.
Eu implementei o padrão “The Looper”, como Adam Argyle o chama. Basicamente, assim como os grupos de entrada de rádio, apenas o elemento ativo está na sequência de guias do documento. Isso é feito usando tabindex
atributos. Quando um botão está em foco, todo o grupo pode ser percorrido usando as teclas de seta. O fluxo de conteúdo da direita para a esquerda é contabilizado.
Atributos
Meu componente suporta um disabled
atributo.
<dots-component disabled> <!-- buttons --></dots-component>
Os atributos podem ser alternados com JavaScript de duas maneiras:
const dots = document.querySelector('dots-component');dots.disabled = true; // set directlydots.removeAttribute('disabled'); // use methods
O disabled
atributo é transmitido a todos <button>
os filhos que sincronizam seu estado.
Jeremy Keith sugere usar odata-
prefixo para atributos personalizados para evitar conflitos com possíveis atributos globais futuros. Recentemente critiquei o HTMX por usarhx-
overdata-
. Angular faz o mesmo comng-
. Mas esses dois exemplos geralmente não usam elementos personalizados. Elementos personalizados permitem atributos não prefixados, o que me surpreende. Há muito mais discussão, mas parece que o cavalo fugiu dessa.
Acho que meu disabled
atributo é seguro — digo hesitante — porque estou imitando um que já existe. Dito isto, não é idêntico porque a :disabled
pseudoclasse CSS não funciona. Então talvez eu esteja cometendo o pior pecado…
Existe uma API ElementInternals mais recente que permite estado personalizado:
class DotsElement extends HTMLElement { #internals; connectedCallback() { this.#internals = this.attachInternals(); this.#internals.states.add('disabled'); }}
O estado personalizado pode ser selecionado com CSS:
dots-component { &:state(disabled) { pointer-events: none; }}
Este estado não se traduz diretamente em atributos HTML; eles devem ser conectados manualmente. Para o meu caso de uso, ainda preciso propagar o disabled
atributo. No código completo estou usando métodos get/set junto com attributeChangedCallback
.
A questão permanece: devo manter disabled
ou usar data-disabled
o <dots-component>
elemento personalizado?
Eventos e Métodos
Meu elemento personalizado despacha um change
evento.
const dots = document.querySelector('dots-component');dots.addEventListener('change', (ev) => { console.log( `Index: ${ev.detail.activeIndex}`, `Label: "${ev.target.activeElement.innerText}"` );});
O elemento também possui alguns métodos:
dots.goto(2);dots.previous();dots.next();
Estes devem ser autoexplicativos.
Próxima Etapa
Conforme mencionado, pretendo construir a(s) outra(s) peça(s) de um padrão de carrossel. Já tentei isso antes com meu Carrossel responsivo principalmente a CSS. Essa versão não usa elementos personalizados e os “pontos” foram uma reflexão tardia.
Aliás, estou usando o nome <dots-component>
por falta de ideia melhor. Sugestões são bem-vindas. Tenha um bom dia!