Como usar o campo nativo de busca e filtros sem dependências externas
1. Fundamentos da busca nativa no navegador
A API nativa do navegador oferece eventos como input e change para capturar consultas em tempo real. A principal diferença entre busca local (client-side) e remota (server-side) está no local onde a filtragem ocorre: na busca local, todo o conjunto de dados já está carregado no navegador, enquanto na remota, cada consulta dispara uma requisição ao servidor.
Para acessibilidade, utilize atributos como aria-label no campo de busca, role="search" no formulário e gerencie o foco automático com autofocus ou element.focus().
<!-- Exemplo básico de campo de busca acessível -->
<form role="search" aria-label="Busca de temas">
<input
type="search"
id="campoBusca"
aria-label="Digite o nome do tema"
placeholder="Buscar tema..."
autofocus
/>
</form>
2. Estrutura HTML semântica para campo de busca e filtros
Construa um formulário semântico usando <input type="search"> para o campo principal e <datalist> para sugestões pré-definidas. Organize filtros com <fieldset> e <legend> para agrupar opções relacionadas.
<form role="search" aria-label="Filtros de temas">
<fieldset>
<legend>Filtros disponíveis</legend>
<label for="busca">Buscar tema:</label>
<input type="search" id="busca" list="sugestoes" />
<datalist id="sugestoes">
<option value="Tecnologia">
<option value="Saúde">
<option value="Educação">
</datalist>
<label for="categoria">Categoria:</label>
<select id="categoria">
<option value="">Todas</option>
<option value="ciencia">Ciência</option>
<option value="arte">Arte</option>
<option value="historia">História</option>
</select>
<label>
<input type="checkbox" id="apenasFavoritos" />
Apenas favoritos
</label>
</fieldset>
<div aria-live="polite" id="resultados">Nenhum resultado ainda</div>
</form>
3. Lógica de filtragem com JavaScript puro: algoritmos básicos
Implemente busca textual com String.prototype.includes() combinado com toLowerCase() para tornar a busca case-insensitive. Para múltiplos filtros, utilize operadores lógicos AND/OR com arrays e o método filter().
const temas = [
{ id: 1, nome: "Inteligência Artificial", categoria: "tecnologia" },
{ id: 2, nome: "Medicina Preventiva", categoria: "saude" },
{ id: 3, nome: "Arte Renascentista", categoria: "arte" }
];
function filtrarTemas(termoBusca, categoria, apenasFavoritos) {
return temas.filter(tema => {
const nome = tema.nome.toLowerCase();
const termo = termoBusca.toLowerCase();
const correspondeBusca = nome.includes(termo);
const correspondeCategoria = !categoria || tema.categoria === categoria;
return correspondeBusca && correspondeCategoria;
});
}
Para tratamento de acentos, utilize normalize('NFD').replace(/[\u0300-\u036f]/g, '') antes da comparação.
4. Otimização de performance para grandes listas
Implemente debouncing para evitar execuções excessivas a cada tecla pressionada. Uma função debounce atrasa a execução até que o usuário pare de digitar por um intervalo definido.
function debounce(fn, delay = 300) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const buscarComDebounce = debounce((evento) => {
const termo = evento.target.value;
const resultados = filtrarTemas(termo, '', false);
exibirResultados(resultados);
}, 400);
Para grandes listas (acima de 10.000 itens), limite a exibição com slice() e implemente scroll manual para evitar travamentos na interface.
5. Filtros avançados com expressões regulares e dados aninhados
Use RegExp para padrões complexos de busca, como palavras que começam com determinada letra ou que contenham padrões específicos.
function buscarComRegex(termo, lista) {
const regex = new RegExp(termo, 'gi');
return lista.filter(item => regex.test(item.nome));
}
// Exemplo: buscar temas que começam com "A"
const temasComA = buscarComRegex('^a', temas);
Para dados aninhados, utilize reduce() com recursão controlada para percorrer objetos complexos:
function buscarEmObjeto(objeto, termo) {
return Object.values(objeto).reduce((resultados, valor) => {
if (typeof valor === 'object') {
return resultados.concat(buscarEmObjeto(valor, termo));
}
if (String(valor).toLowerCase().includes(termo.toLowerCase())) {
resultados.push(objeto);
}
return resultados;
}, []);
}
6. Gerenciamento de estado e reatividade sem frameworks
Armazene o estado de busca e filtros em um objeto central (state) e atualize a interface com funções puras utilizando documentFragment para evitar múltiplas manipulações do DOM.
const state = {
termoBusca: '',
categoria: '',
apenasFavoritos: false,
resultados: []
};
function atualizarEstado(novoEstado) {
Object.assign(state, novoEstado);
renderizarResultados();
}
function renderizarResultados() {
const fragment = document.createDocumentFragment();
state.resultados.forEach(tema => {
const div = document.createElement('div');
div.textContent = tema.nome;
fragment.appendChild(div);
});
const container = document.getElementById('resultados');
container.innerHTML = '';
container.appendChild(fragment);
}
Para persistência temporária, utilize sessionStorage:
function salvarEstado() {
sessionStorage.setItem('filtrosTemas', JSON.stringify(state));
}
function carregarEstado() {
const salvo = sessionStorage.getItem('filtrosTemas');
if (salvo) Object.assign(state, JSON.parse(salvo));
}
7. Feedback visual e tratamento de casos extremos
Exiba "Nenhum resultado encontrado" quando a filtragem retornar vazio, e mostre a contagem de itens encontrados.
function exibirResultados(lista) {
const container = document.getElementById('resultados');
if (lista.length === 0) {
container.innerHTML = '<p role="alert">Nenhum resultado encontrado</p>';
return;
}
container.innerHTML = `<p>${lista.length} tema(s) encontrado(s)</p>`;
// Adicionar itens...
}
Para indicadores de carregamento em buscas assíncronas, utilize um spinner CSS simples:
<spinner class="spinner" aria-hidden="true" style="display:none;"></spinner>
<style>
.spinner {
width: 24px;
height: 24px;
border: 3px solid #ccc;
border-top-color: #333;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
Implemente um botão "Resetar" que limpa todos os filtros e restaura a lista original:
document.getElementById('resetar').addEventListener('click', () => {
document.getElementById('busca').value = '';
document.getElementById('categoria').value = '';
document.getElementById('apenasFavoritos').checked = false;
state.termoBusca = '';
state.categoria = '';
state.apenasFavoritos = false;
state.resultados = [...temas];
renderizarResultados();
});
8. Testes e depuração da busca nativa
Simule eventos de teclado e clique com dispatchEvent para testar automaticamente a lógica de busca:
function simularDigitacao(elemento, texto) {
elemento.value = texto;
elemento.dispatchEvent(new Event('input', { bubbles: true }));
}
const campo = document.getElementById('busca');
simularDigitacao(campo, 'Inteligência');
Meça o tempo de filtragem com performance.now() para identificar gargalos:
const inicio = performance.now();
const resultados = filtrarTemas(termo, categoria, favoritos);
const fim = performance.now();
console.log(`Tempo de filtragem: ${fim - inicio}ms`);
Teste a acessibilidade garantindo que leitores de tela anunciem as mudanças nos resultados através do aria-live e que o foco seja gerenciado corretamente após cada filtragem.
Referências
- MDN Web Docs: Element input event — Documentação oficial sobre o evento
inputpara captura de mudanças em tempo real em campos de formulário. - MDN Web Docs: ARIA: role search — Guia sobre o uso correto do
role="search"para acessibilidade em formulários de busca. - CSS-Tricks: Debouncing and Throttling Explained — Artigo detalhado sobre técnicas de debouncing e throttling para otimização de performance em eventos de busca.
- W3C: Using ARIA live regions — Documentação do W3C sobre regiões
aria-livepara notificar leitores de tela sobre mudanças dinâmicas nos resultados. - Google Developers: Speed up search with debounce — Tutorial do Google sobre implementação de debounce para melhorar a performance de campos de busca em páginas web.