Observers (Intersection, Mutation, Resize)
LidoNesta aula, exploramos os Observers do JavaScript: IntersectionObserver, MutationObserver e ResizeObserver. Aprendemos o que cada um faz, seus casos de uso práticos (como lazy-load e detecção de redimensionamento) e implementamos exemplos completos. Ao final, você será capaz de aplicar esses observers para criar interfaces mais dinâmicas e eficientes.
Bem-vindo a mais uma aula do nosso curso de JavaScript! Hoje vamos explorar um dos recursos mais poderosos e modernos da plataforma: os Observers. Eles permitem que o JavaScript observe mudanças no DOM, no tamanho de elementos e na visibilidade de elementos na viewport, de forma assíncrona e eficiente, sem a necessidade de loops de verificação contínua.
Nesta aula, vamos nos aprofundar em três tipos específicos: IntersectionObserver, MutationObserver e ResizeObserver. Cada um tem um propósito distinto, mas juntos eles formam um kit de ferramentas essencial para criar interfaces ricas e reativas. Vamos entender o que cada um faz, quando usar e como implementar exemplos práticos.
O que cada um faz
Os Observers são classes do JavaScript que permitem observar mudanças específicas no ambiente do navegador. Eles são fundamentais para otimizar o desempenho, pois trabalham de forma assíncrona e não bloqueiam a thread principal. Vamos conhecer cada um deles:
- IntersectionObserver: Observa quando um elemento entra ou sai da área visível (viewport) ou de um contêiner específico. É ideal para implementar lazy-load de imagens, carregar mais conteúdo ao rolar a página (infinite scroll) e animações baseadas em scroll.
- MutationObserver: Observa mudanças no DOM, como adição ou remoção de nós, alterações de atributos e mudanças no texto. É útil para reagir a mudanças dinâmicas, como quando um script externo modifica o conteúdo da página.
- ResizeObserver: Observa mudanças no tamanho de um elemento (largura e altura). Perfeito para criar layouts responsivos e ajustar elementos quando o tamanho do contêiner muda, como gráficos ou componentes que precisam se redimensionar.
Cada um desses observers é instanciado com uma função de callback que é executada quando o evento ocorre. Eles também possuem métodos para iniciar e parar a observação, como observe() e disconnect().
Casos de uso (lazy-load, etc.)
Os observers têm uma ampla gama de aplicações práticas. Vamos listar alguns casos de uso comuns:
- Lazy-load de imagens e iframes: Com IntersectionObserver, podemos carregar recursos apenas quando o usuário estiver prestes a vê-los, economizando banda e melhorando o tempo de carregamento inicial.
- Infinite scroll: Ao observar um elemento sentinela (um marcador no final da lista), podemos carregar mais conteúdo automaticamente quando o usuário rolar até ele.
- Animações ao rolar: Podemos adicionar classes CSS quando um elemento entra na viewport, disparando animações.
- Reagir a alterações no DOM: Com MutationObserver, podemos, por exemplo, atualizar um contador de elementos quando novos itens são adicionados, ou detectar quando um script injeta conteúdo.
- Layout responsivo: Com ResizeObserver, podemos redimensionar gráficos (como canvas) ou ajustar a largura de elementos quando o contêiner muda de tamanho.
- Detecção de colapso de menu: Se um menu lateral colapsa, o ResizeObserver pode detectar a mudança de largura e reorganizar o conteúdo.
Esses são apenas alguns exemplos. A versatilidade desses observers permite criar experiências de usuário mais fluidas e reativas.
Exemplos
Vamos colocar a mão na massa com exemplos práticos para cada observer. Lembre-se de que os exemplos são auto-contidos e podem ser executados no console do navegador ou em um arquivo HTML.
Exemplo 1: IntersectionObserver – Lazy-load de imagens
Neste exemplo, vamos criar um lazy-load simples. Usaremos um data-src para armazenar o caminho real da imagem e, quando ela entrar na viewport, trocaremos o src.
// Seleciona todas as imagens com data-src
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // carrega a imagem real
img.classList.add('loaded');
observer.unobserve(img); // para de observar a imagem
}
});
}, { rootMargin: '0px 0px 50px 0px' }); // ativa 50px antes de entrar
images.forEach(img => observer.observe(img));
Nesse código, criamos um observer que verifica se a imagem está intersectando a viewport. Quando sim, definimos o src e removemos a observação, evitando trabalho desnecessário.
Exemplo 2: MutationObserver – Detectar novos elementos
Vamos observar um contêiner e detectar quando um novo item é adicionado. Usaremos um botão para adicionar itens dinamicamente.
// Elemento a ser observado
const container = document.getElementById('container');
// Configuração do observer
const config = { childList: true, subtree: true };
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('Novo elemento adicionado:', mutation.addedNodes);
// Atualizar contador, etc.
}
}
});
observer.observe(container, config);
// Simula adição de um elemento
const newItem = document.createElement('div');
newItem.textContent = 'Item novo';
container.appendChild(newItem);
O MutationObserver reage a qualquer adição ou remoção de nós filhos. Podemos usar isso para atualizar a interface ou disparar outras ações.
Exemplo 3: ResizeObserver – Redimensionar um elemento
Vamos acompanhar o tamanho de uma div e exibir suas dimensões em tempo real.
const box = document.getElementById('box');
const output = document.getElementById('output');
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
output.textContent = `Largura: ${width}px, Altura: ${height}px`;
}
});
resizeObserver.observe(box);
Assim, sempre que o tamanho da caixa mudar, o texto é atualizado. Isso é útil para gráficos responsivos.
Boas práticas e observações finais
- Desobserve quando não for mais necessário: Use
unobserve()oudisconnect()para evitar vazamentos de memória. - Use rootMargin com moderação: O IntersectionObserver aceita rootMargin para expandir ou reduzir a área de detecção, mas use com cuidado.
- Combine observers: Em situações complexas, você pode usar vários observers no mesmo elemento.
- Teste em diferentes navegadores: Apesar do amplo suporte, é bom verificar a compatibilidade, especialmente em navegadores mais antigos.
Esses observers são suportados em todos os navegadores modernos e são uma excelente adição ao seu arsenal de desenvolvimento.
Referências
- MDN: IntersectionObserver
- MDN: MutationObserver
- MDN: ResizeObserver
- W3C: Intersection Observer Specification
- W3C: Mutation Observer Specification
- W3C: Resize Observer Specification
- Can I use: IntersectionObserver
- Can I use: MutationObserver
- Can I use: ResizeObserver
Exercícios
- Exercício 1: Crie um IntersectionObserver que adicione uma classe 'visible' a um elemento quando ele estiver 50% visível na viewport. Escreva o código necessário.
- Exercício 2: Explique a diferença entre
observe()edisconnect()no MutationObserver e dê um exemplo de quando usar cada um. - Exercício 3: Usando ResizeObserver, escreva um código que ajuste dinamicamente a altura de uma imagem para manter a proporção 16:9 quando a largura do contêiner mudar.
- Exercício 4: Qual é a vantagem de usar IntersectionObserver em vez de eventos de scroll para implementar lazy-load?
- Exercício 5: Crie um MutationObserver que observe mudanças de atributo em um elemento e imprima o novo valor do atributo 'data-status'.
const element = document.getElementById('meu-elemento');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.5 });
observer.observe(element);
observe() inicia a observação de um elemento específico, enquanto disconnect() para toda a observação do observer. Exemplo: usar observe() para começar a monitorar mudanças em um formulário, e disconnect() quando o formulário for enviado e não precisarmos mais monitorar.const container = document.getElementById('container');
const img = document.getElementById('imagem');
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const width = entry.contentRect.width;
img.style.height = (width * 9 / 16) + 'px';
}
});
resizeObserver.observe(container);
const target = document.getElementById('alvo');
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'attributes' && mutation.attributeName === 'data-status') {
console.log('Novo data-status:', target.dataset.status);
}
});
});
observer.observe(target, { attributes: true, attributeFilter: ['data-status'] });