Evitando armadilhas com o evento resize em JavaScript

O evento resize da janela parece simples—até que sua aplicação comece a travar. Se você já se perguntou por que seu código JavaScript responsivo causa problemas de performance, provavelmente está enfrentando uma das armadilhas mais comuns dos eventos resize em JavaScript que afetam aplicações frontend.
Este artigo explora por que os eventos resize podem prejudicar a performance, como otimizá-los com throttling e debouncing, e quando contornar completamente o JavaScript usando soluções CSS modernas e APIs.
Principais Pontos
- Eventos resize disparam centenas de vezes por segundo durante o redimensionamento da janela, causando sérios problemas de performance
- Throttling e debouncing são técnicas essenciais para limitar a frequência de execução dos manipuladores de eventos
- Alternativas modernas como a API ResizeObserver e CSS container queries frequentemente oferecem melhor performance
- A limpeza adequada de event listeners previne vazamentos de memória em aplicações de produção
O Custo de Performance Oculto dos Eventos Resize em JavaScript
Quando um usuário arrasta para redimensionar a janela do navegador, o evento resize não dispara apenas uma vez—ele dispara continuamente. Um simples arrastar da janela pode acionar o manipulador de evento centenas de vezes por segundo, sobrecarregando a thread principal com chamadas de função.
// Isto registra centenas de vezes durante uma única ação de redimensionamento
window.addEventListener('resize', () => {
console.log(`Tamanho da janela: ${window.innerWidth}x${window.innerHeight}`);
});
Cada execução do evento bloqueia a thread principal, impedindo que o navegador lide com outras tarefas críticas como atualizações de renderização ou processamento de interações do usuário. O resultado? Animações travadas, interfaces não responsivas e usuários frustrados.
Por Que as Armadilhas dos Eventos Resize em JavaScript Importam para a Performance
Disparos Excessivos de Eventos e Bloqueio da Thread Principal
O evento resize dispara para cada mudança de pixel durante o redimensionamento da janela. Se seu manipulador realiza cálculos complexos ou manipulações do DOM, você está essencialmente executando operações custosas centenas de vezes por segundo.
Considere este padrão comum:
window.addEventListener('resize', () => {
const elements = document.querySelectorAll('.responsive-element');
elements.forEach(el => {
// Cálculos complexos para cada elemento
el.style.width = calculateOptimalWidth(el);
});
});
Este código recalcula e atualiza múltiplos elementos continuamente durante o redimensionamento, criando um gargalo de performance.
Layout Thrashing: O Assassino Silencioso da Performance
A armadilha mais insidiosa dos eventos resize em JavaScript ocorre quando você lê dimensões de elementos e imediatamente escreve novos estilos. Este padrão, chamado layout thrashing, força o navegador a recalcular layouts sincronamente:
window.addEventListener('resize', () => {
// Força cálculo de layout
const width = element.offsetWidth;
// Invalida o layout
element.style.width = (width * 0.8) + 'px';
// Força outro cálculo de layout
const height = element.offsetHeight;
});
Cada leitura de dimensão aciona um recálculo completo de layout, multiplicado por centenas de eventos resize.
Técnicas de Otimização Essenciais: Throttling e Debouncing
Implementando Throttle para Eventos Resize
O throttling limita com que frequência seu manipulador de resize executa, tipicamente para 60fps (a cada 16ms) ou menos:
function throttle(func, delay) {
let lastExecTime = 0;
let timeoutId;
return function (...args) {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func.apply(this, args);
lastExecTime = currentTime;
} else {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
lastExecTime = Date.now();
}, delay - (currentTime - lastExecTime));
}
};
}
const throttledResize = throttle(() => {
// Isto executa no máximo uma vez a cada 100ms
updateLayout();
}, 100);
window.addEventListener('resize', throttledResize);
Discover how at OpenReplay.com.
Debouncing para Ações de Redimensionamento Completas
O debouncing aguarda até que o redimensionamento pare antes de executar:
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
const debouncedResize = debounce(() => {
// Executa 250ms após o redimensionamento parar
recalculateLayout();
}, 250);
window.addEventListener('resize', debouncedResize);
// Não esqueça da limpeza
// window.removeEventListener('resize', debouncedResize);
Alternativas Modernas aos Eventos Resize da Janela
Soluções CSS-First: Media Queries e Container Queries
Frequentemente, você pode evitar completamente as armadilhas dos eventos resize em JavaScript usando CSS:
/* Media queries para responsividade baseada na janela */
@media (max-width: 768px) {
.sidebar { display: none; }
}
/* Container queries para responsividade baseada em componentes */
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card { grid-template-columns: 1fr 1fr; }
}
Para detecção de media queries baseada em JavaScript, use matchMedia:
const mediaQuery = window.matchMedia('(max-width: 768px)');
mediaQuery.addEventListener('change', (e) => {
// Dispara apenas quando cruza o breakpoint
if (e.matches) {
showMobileMenu();
}
});
API ResizeObserver: A Alternativa Amigável à Performance
O ResizeObserver fornece monitoramento de tamanho específico por elemento sem penalidades de performance:
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
// O navegador fornece o tamanho—sem reflow forçado
const { width, height } = entry.contentRect;
updateElementLayout(entry.target, width, height);
}
});
resizeObserver.observe(document.querySelector('.responsive-container'));
// Limpeza quando terminar
// resizeObserver.disconnect();
Melhores Práticas para Manipulação de Resize Pronta para Produção
- Sempre limpe os event listeners para prevenir vazamentos de memória
- Escolha a ferramenta certa: Use CSS para estilização, ResizeObserver para monitoramento de elementos, e eventos resize com throttle apenas quando necessário
- Meça o impacto na performance usando o painel Performance do Chrome DevTools
- Considere listeners passivos para melhor performance de scroll quando aplicável
Conclusão
Ao compreender essas armadilhas dos eventos resize em JavaScript e implementar soluções apropriadas, você pode construir interfaces responsivas que performam suavemente em todos os dispositivos. A chave é escolher a abordagem certa para seu caso de uso específico—seja soluções baseadas em CSS, APIs modernas, ou manipuladores de eventos adequadamente otimizados. Comece com CSS onde possível, use ResizeObserver para monitoramento específico de elementos, e reserve eventos resize com throttle ou debounce para casos onde o monitoramento a nível de janela é verdadeiramente necessário.
Perguntas Frequentes
O throttling limita a execução a um intervalo fixo durante o redimensionamento contínuo, executando regularmente enquanto o usuário arrasta. O debouncing aguarda até que o redimensionamento pare completamente antes de executar uma vez. Use throttling para atualizações em tempo real e debouncing para cálculos finais.
O ResizeObserver tem excelente suporte em navegadores modernos incluindo Chrome, Firefox, Safari e Edge. Para navegadores mais antigos, use um polyfill ou recorra a eventos resize com throttle com detecção de recursos para garantir compatibilidade.
Use container queries quando o estilo depende do tamanho de um elemento ao invés da viewport. Elas são perfeitas para designs baseados em componentes, layouts de cards e tipografia responsiva sem overhead de JavaScript ou preocupações com performance.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.