Skip to content

Tudo para WordPress, desenvolvimento web — e não só

📱 JavaScript: como detetar a largura do ecrã, o equivalente à @media query em código

📱 JavaScript: como detetar a largura do ecrã, o equivalente à @media query em código

Por vezes, o layout já está feito, as media queries CSS estão no lugar, mas precisa de detetar o comportamento num breakpoint específico diretamente em JavaScript. Mostrar uma popup apenas em dispositivos móveis, reorganizar uma grelha ao redimensionar, disparar uma animação quando o ecrã é «estreito»: tudo isto exige que o script conheça a largura atual da janela.

O problema é que os developers optam frequentemente pelo caminho difícil: leem window.innerWidth, aplicam throttle ao resize, comparam com números mágicos e acabam com código frágil que vive separado dos breakpoints CSS. No entanto, os browsers têm há muito um método que funciona com as mesmas expressões de media que o CSS.

Abaixo estão três abordagens práticas: desde o moderno matchMedia (funciona como @media em CSS) até uma variante jQuery para projetos legados. Com exemplos ao vivo que pode copiar e executar agora mesmo.

💡 Visão geral rápida:

  • matchMedia: um método nativo que aceita uma expressão de media CSS e indica se esta corresponde atualmente; ideal para sincronizar a lógica JS com os breakpoints CSS
  • resize + matchMedia: uma combinação que responde a alterações da janela do browser; o script fica a saber que um breakpoint foi ultrapassado instantaneamente, sem consultar periodicamente o innerWidth
  • Variante jQuery: para projetos onde o jQuery já está na página; o mesmo resize, mas sem o matchMedia nativo; a comparação é feita via $(window).width()

MatchMedia: a fonte única de verdade para a largura do ecrã

A principal desvantagem do window.innerWidth é que ele não sabe nada sobre os seus breakpoints CSS. Define 768px nas media queries, depois escreve if (window.innerWidth < 768) em JS e, mais cedo ou mais tarde, o arredondamento ou a barra de scroll quebram a sincronização.

O window.matchMedia() resolve este problema de forma radical: aceita a mesma string de expressão de media que a regra CSS @media. O resultado é um objeto MediaQueryList com uma propriedade .matches (true / false). Sem números mágicos, sem desalinhamento com o layout.

Sintaxe básica:

1const mq = window.matchMedia("(min-width: 768px)");
2
3if (mq.matches) {
4 console.log("Tablet or wider — 768px+");
5} else {
6 console.log("Mobile resolution — less than 768px");
7}

A mesma chamada matchMedia("(min-width: 768px)") é avaliada pelo browser usando as mesmas regras que @media (min-width: 768px) em CSS. Se a barra lateral estiver oculta neste breakpoint em CSS, o JS «vê» o mesmo e pode, por exemplo, ocultar o menu mobile.

Além do .matches, o objeto MediaQueryList fornece uma propriedade .media (a string de consulta original) e um método addEventListener para subscrever alterações. Isto significa que, depois de declarar um breakpoint numa configuração, pode usá-lo tanto em CSS como em JS sem duplicar números mágicos: basta extrair 768 para uma constante e inseri-la em ambos os locais.

Responder ao resize sem throttle e workarounds

Verificar «agora mesmo» é apenas metade da batalha. A verdadeira magia começa quando o script fica a saber que um breakpoint foi ultrapassado no momento em que a janela muda.

O MediaQueryList tem um evento change que dispara exatamente quando o valor .matches alterna. Não a cada pixel de resize, mas apenas ao cruzar o limite:

1const mq = window.matchMedia("(min-width: 500px)");
2
3mq.addEventListener("change", function (e) {
4 if (e.matches) {
5 console.log("Screen expanded to 500px or more");
6 } else {
7 console.log("Screen narrowed to less than 500px");
8 }
9});

Para retrocompatibilidade com browsers mais antigos, pode obter o mesmo resultado através de um handler geral de resize no window:

1window.addEventListener("resize", function () {
2 if (window.matchMedia("(min-width: 500px)").matches) {
3 console.log("Screen width — at least 500px");
4 } else {
5 console.log("Less than 500px");
6 }
7});
Consola do navegador a mostrar mensagens de largura do ecrã durante redimensionamento

A diferença é simples: change no MediaQueryList é uma abordagem orientada a eventos (sem chamadas extra durante o resize dentro de um intervalo), enquanto resize no window é um fallback familiar para qualquer developer.

Intervalo de largura: entre dois breakpoints

Uma tarefa comum é «de 769px a 1024px». O matchMedia funciona aqui como em CSS: combine min-width e max-width numa única expressão:

1window.addEventListener("resize", function () {
2 if (
3 window.matchMedia("(min-width: 769px)").matches &&
4 window.matchMedia("(max-width: 1024px)").matches
5 ) {
6 console.log("Tablet range: 769px – 1024px");
7 } else {
8 console.log("Outside the tablet range");
9 }
10});

Ou como uma expressão única (os browsers compreendem media queries compostas tal como em CSS):

1const tablet = window.matchMedia("(min-width: 769px) and (max-width: 1024px)");
2
3tablet.addEventListener("change", function (e) {
4 console.log(e.matches ? "Entered tablet range" : "Left tablet range");
5});

Qual a variante a escolher? change no MediaQueryList quando precisa de capturar o momento exato em que o limite é ultrapassado (por exemplo, para reestruturar a árvore DOM). resize + matchMedia quando a lógica é mais simples e só precisa de «verificar agora» sem subscrever transições futuras.

Variante jQuery: quando o matchMedia não está disponível

Se o projeto usar jQuery e polyfills não forem uma opção, o mesmo resultado é obtido comparando $(window).width() com um valor limite:

1jQuery(document).ready(function ($) {
2 if ($(window).width() > 1000) {
3 console.log("Screen width greater than 1000px");
4 } else {
5 console.log("Screen width 1000px or less");
6 }
7});

Este código é executado uma vez no carregamento da página. Para acompanhar o resize, envolva a verificação num handler:

1jQuery(document).ready(function ($) {
2 function checkWidth() {
3 if ($(window).width() > 1000) {
4 console.log("Width > 1000px");
5 } else {
6 console.log("Width ≤ 1000px");
7 }
8 }
9
10 checkWidth(); // initial run
11 $(window).on("resize", checkWidth);
12});

Mas lembre-se: $(window).width() e matchMedia podem diferir em alguns pixels devido à barra de scroll. O matchMedia trabalha com a viewport, tal como as regras CSS. É por isso que recomendo o método nativo para todo o código novo.

MatchMedia vs innerWidth: uma breve comparação

Critério

matchMedia

innerWidth

Sincronização com CSS

Total (mesmas expressões)

Ajuste manual de números

Evento ao cruzar breakpoint

change no MediaQueryList

Apenas resize no window

Tratamento da barra de scroll

Igual ao CSS (viewport)

Dependente do browser

Deteção de tema escuro / prefers-reduced-motion

Sim (qualquer expressão de media)

Não

⁉️🤔 Perguntas frequentes

O que é melhor: matchMedia ou window.innerWidth?

O matchMedia é sempre melhor quando a lógica está ligada aos breakpoints CSS. Usa as mesmas regras que @media, eliminando discrepâncias de um pixel ou dois devido à barra de scroll ou ao zoom. O innerWidth só é apropriado quando precisa do valor numérico exato (por exemplo, para calcular quantos elementos cabem), e não do facto «ecrã mais largo do que N pixels».

O matchMedia funciona em browsers mais antigos?

Sim, o suporte é amplo: todos os browsers modernos, incluindo mobile, e Internet Explorer 10+. O IE9 e versões anteriores ficam de fora; para eles terá de usar window.innerWidth ou a variante jQuery deste artigo. Na prática, a quota do IE9 em 2026 aproxima-se de zero.

O matchMedia pode verificar mais do que apenas a largura?

Sim, o método aceita qualquer expressão de media CSS válida. Por exemplo: (orientation: portrait) para a orientação do dispositivo; (prefers-color-scheme: dark) para o tema escuro no SO; (prefers-reduced-motion: reduce) para um pedido de desativação de animações. Funciona exatamente como em CSS: window.matchMedia("(prefers-color-scheme: dark)").matches devolve true se o utilizador tiver o tema escuro ativado.

Preciso de remover o handler change ao sair da página?

Em código moderno, não. O browser limpa a memória automaticamente ao descarregar a página. Em SPAs (React, Vue), onde um componente é montado e desmontado sem recarregar a página, deve guardar uma referência para o handler e removê-la via removeEventListener no componentWillUnmount / onUnmounted; caso contrário, terá fugas de memória e disparos repetidos em componentes «mortos».

Porque é que $(window).width() e matchMedia mostram por vezes larguras diferentes?

Porque medem coisas diferentes. O matchMedia trabalha com a largura da viewport (a área de visualização CSS), a mesma que as media queries usam. O $(window).width() / window.innerWidth inclui a largura da barra de scroll vertical, se estiver presente. A diferença é geralmente de 15 a 17 px, exatamente a largura da barra de scroll. Daí a regra: se está a ligar a lógica a breakpoints CSS, use matchMedia; se precisa da largura «real» da janela em pixels, use innerWidth.

Então o que usar: a conclusão final

Para código novo, a resposta é clara: window.matchMedia(). Vive no mesmo contrato que o seu CSS, não requer ajuste manual de números para cada breakpoint e fornece um modelo de eventos de «alternou, você sabe» em vez de consulta constante da largura.

Um cenário típico onde a diferença se nota de imediato: está a construir um cartão de produto que mostra uma galeria de quatro imagens em desktop, mas uma única imagem deslizável em mobile. Em CSS tem @media (max-width: 768px) a alterar o layout. Em JS, em vez de if (window.innerWidth <= 768) escreve matchMedia("(max-width: 768px)"), e o browser garante que a condição JS dispara exatamente quando o layout muda. Sem «quase funcionava», sem bugs nos 767px por causa da barra de scroll.

Deixe a abordagem jQuery com $(window).width() para a manutenção de projetos antigos: funciona, mas obriga-o a duplicar breakpoints no código e diverge silenciosamente do CSS pela largura da barra de scroll.

E se quiser ver tudo o que foi descrito acima em ação, aqui está um tutorial de 10 minutos onde o matchMedia é explicado desde a invocação até ao modelo de eventos:

Experimente substituir o if (innerWidth < 768) mais próximo por matchMedia("(min-width: 768px)") e sentirá imediatamente como o código fica muito mais limpo.