
📱 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 CSSresize+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 oinnerWidth- Variante jQuery: para projetos onde o jQuery já está na página; o mesmo
resize, mas sem omatchMedianativo; 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:
1 const mq = window.matchMedia("(min-width: 768px)"); 2 3 if (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:
1 const mq = window.matchMedia("(min-width: 500px)"); 2 3 mq.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:
1 window.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 });

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:
1 window.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):
1 const tablet = window.matchMedia("(min-width: 769px) and (max-width: 1024px)"); 2 3 tablet.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:
1 jQuery(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:
1 jQuery(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 |
|
|
|---|---|---|
Sincronização com CSS | Total (mesmas expressões) | Ajuste manual de números |
Evento ao cruzar breakpoint |
| Apenas |
Tratamento da barra de scroll | Igual ao CSS (viewport) | Dependente do browser |
Deteção de tema escuro / | 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. OinnerWidthsó é 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.innerWidthou 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)").matchesdevolvetruese 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
removeEventListenernocomponentWillUnmount/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
matchMediatrabalha com a largura da viewport (a área de visualização CSS), a mesma que as media queries usam. O$(window).width()/window.innerWidthinclui 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, usematchMedia; se precisa da largura «real» da janela em pixels, useinnerWidth.
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.



