Media Queries en JavaScript
Utiliser les media queries en JavaScript permet de détecter les changements de taille d'écran et d'adapter le comportement de votre site en conséquence. C'est particulièrement utile pour activer/désactiver des fonctionnalités selon la taille de l'écran.
API window.matchMedia()
L'API window.matchMedia() permet de tester et d'écouter les changements de media queries en JavaScript.
Syntaxe de base
const mediaQuery = window.matchMedia('(max-width: 768px)');
if (mediaQuery.matches) {
// Le viewport fait moins de 768px de large
console.log('Mobile détecté');
} else {
// Le viewport fait plus de 768px de large
console.log('Desktop détecté');
}Écouter les changements
Pour réagir aux changements de taille d'écran en temps réel, utilisez addListener() ou addEventListener() :
Méthode moderne (recommandée)
const mediaQuery = window.matchMedia('(max-width: 768px)');
function handleMediaChange(e) {
if (e.matches) {
// Media query correspond maintenant
console.log('Mode mobile activé');
} else {
// Media query ne correspond plus
console.log('Mode desktop activé');
}
}
// Écouter les changements
mediaQuery.addEventListener('change', handleMediaChange);
// Exécuter immédiatement
handleMediaChange(mediaQuery);Méthode avec addListener (ancienne, toujours supportée)
const mediaQuery = window.matchMedia('(max-width: 768px)');
function handleMediaChange(e) {
if (e.matches) {
console.log('Mode mobile');
} else {
console.log('Mode desktop');
}
}
mediaQuery.addListener(handleMediaChange);
handleMediaChange(mediaQuery);Exemples pratiques
Détecter mobile vs desktop
const isMobile = window.matchMedia('(max-width: 768px)');
function handleMobileChange(e) {
if (e.matches) {
// Actions pour mobile
document.body.classList.add('is-mobile');
document.body.classList.remove('is-desktop');
} else {
// Actions pour desktop
document.body.classList.add('is-desktop');
document.body.classList.remove('is-mobile');
}
}
isMobile.addEventListener('change', handleMobileChange);
handleMobileChange(isMobile);Activer/désactiver une fonctionnalité selon la taille
const isTablet = window.matchMedia('(min-width: 769px) and (max-width: 1024px)');
function handleTabletChange(e) {
if (e.matches) {
// Activer une fonctionnalité spécifique pour tablette
enableTabletFeature();
} else {
// Désactiver la fonctionnalité
disableTabletFeature();
}
}
isTablet.addEventListener('change', handleTabletChange);
handleTabletChange(isTablet);Changer le comportement d'un slider
const isMobile = window.matchMedia('(max-width: 768px)');
function initSlider() {
const slider = document.querySelector('.product-slider');
if (isMobile.matches) {
// Configuration mobile : 1 slide visible
slider.setAttribute('data-slides-per-view', '1');
} else {
// Configuration desktop : 3 slides visibles
slider.setAttribute('data-slides-per-view', '3');
}
}
isMobile.addEventListener('change', initSlider);
initSlider();Afficher/masquer des éléments
const isDesktop = window.matchMedia('(min-width: 1025px)');
function toggleDesktopElements(e) {
const desktopElements = document.querySelectorAll('.desktop-only');
desktopElements.forEach(element => {
element.style.display = e.matches ? 'block' : 'none';
});
}
isDesktop.addEventListener('change', toggleDesktopElements);
toggleDesktopElements(isDesktop);Fonction utilitaire réutilisable
Version simple
function useMediaQuery(query, callback) {
const mediaQuery = window.matchMedia(query);
function handleChange(e) {
callback(e.matches, e);
}
mediaQuery.addEventListener('change', handleChange);
handleChange(mediaQuery);
// Retourner une fonction pour nettoyer l'écouteur
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}
// Utilisation
const cleanup = useMediaQuery('(max-width: 768px)', (isMobile) => {
if (isMobile) {
console.log('Mode mobile');
} else {
console.log('Mode desktop');
}
});
// Pour nettoyer plus tard
// cleanup();Version avec plusieurs callbacks
function useMediaQuery(query) {
const mediaQuery = window.matchMedia(query);
const callbacks = [];
function handleChange(e) {
callbacks.forEach(callback => callback(e.matches, e));
}
mediaQuery.addEventListener('change', handleChange);
return {
matches: mediaQuery.matches,
addListener: (callback) => {
callbacks.push(callback);
callback(mediaQuery.matches, mediaQuery);
},
removeListener: (callback) => {
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
},
cleanup: () => {
mediaQuery.removeEventListener('change', handleChange);
}
};
}
// Utilisation
const mobileQuery = useMediaQuery('(max-width: 768px)');
mobileQuery.addListener((isMobile) => {
console.log('Mobile:', isMobile);
});
mobileQuery.addListener((isMobile) => {
document.body.classList.toggle('mobile', isMobile);
});Media queries courantes
Breakpoints standards
// Mobile
const isMobile = window.matchMedia('(max-width: 768px)');
// Tablet
const isTablet = window.matchMedia('(min-width: 769px) and (max-width: 1024px)');
// Desktop
const isDesktop = window.matchMedia('(min-width: 1025px)');
// Large Desktop
const isLargeDesktop = window.matchMedia('(min-width: 1440px)');Orientation
// Portrait
const isPortrait = window.matchMedia('(orientation: portrait)');
// Landscape
const isLandscape = window.matchMedia('(orientation: landscape)');
isPortrait.addEventListener('change', (e) => {
if (e.matches) {
console.log('Mode portrait');
}
});Préférences utilisateur
// Mode sombre
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
prefersDark.addEventListener('change', (e) => {
if (e.matches) {
document.body.classList.add('dark-mode');
} else {
document.body.classList.remove('dark-mode');
}
});
// Mode réduit (accessibilité)
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
prefersReducedMotion.addEventListener('change', (e) => {
if (e.matches) {
// Désactiver les animations
document.body.classList.add('reduce-motion');
}
});Cas d'usage avancés
Initialiser un slider différemment selon la taille
const isMobile = window.matchMedia('(max-width: 768px)');
let sliderInstance = null;
function initSlider() {
// Détruire l'instance existante si elle existe
if (sliderInstance) {
sliderInstance.destroy();
}
const config = isMobile.matches ? {
slidesPerView: 1,
spaceBetween: 10
} : {
slidesPerView: 3,
spaceBetween: 30
};
sliderInstance = new Swiper('.slider', config);
}
isMobile.addEventListener('change', initSlider);
initSlider();Charger des images différentes selon la taille
const isMobile = window.matchMedia('(max-width: 768px)');
function updateImageSources() {
const images = document.querySelectorAll('img[data-mobile-src]');
images.forEach(img => {
if (isMobile.matches) {
img.src = img.dataset.mobileSrc;
} else {
img.src = img.dataset.desktopSrc;
}
});
}
isMobile.addEventListener('change', updateImageSources);
updateImageSources();Adapter le menu de navigation
const isMobile = window.matchMedia('(max-width: 768px)');
function initNavigation() {
const nav = document.querySelector('.main-nav');
if (isMobile.matches) {
// Menu hamburger pour mobile
nav.classList.add('mobile-menu');
initMobileMenu();
} else {
// Menu horizontal pour desktop
nav.classList.remove('mobile-menu');
initDesktopMenu();
}
}
isMobile.addEventListener('change', initNavigation);
initNavigation();Gérer les événements différemment
const isMobile = window.matchMedia('(max-width: 768px)');
function setupEventListeners() {
const button = document.querySelector('.cta-button');
// Retirer les anciens écouteurs
button.replaceWith(button.cloneNode(true));
const newButton = document.querySelector('.cta-button');
if (isMobile.matches) {
// Tap pour mobile
newButton.addEventListener('touchstart', handleClick);
} else {
// Click pour desktop
newButton.addEventListener('click', handleClick);
}
}
isMobile.addEventListener('change', setupEventListeners);
setupEventListeners();Gestion de plusieurs media queries
Objet de configuration
const breakpoints = {
mobile: window.matchMedia('(max-width: 768px)'),
tablet: window.matchMedia('(min-width: 769px) and (max-width: 1024px)'),
desktop: window.matchMedia('(min-width: 1025px)')
};
function getCurrentBreakpoint() {
if (breakpoints.mobile.matches) return 'mobile';
if (breakpoints.tablet.matches) return 'tablet';
if (breakpoints.desktop.matches) return 'desktop';
return 'unknown';
}
function handleBreakpointChange() {
const current = getCurrentBreakpoint();
document.body.setAttribute('data-breakpoint', current);
console.log('Breakpoint actuel:', current);
}
// Écouter tous les changements
Object.values(breakpoints).forEach(mq => {
mq.addEventListener('change', handleBreakpointChange);
});
handleBreakpointChange();Classe utilitaire complète
class MediaQueryManager {
constructor(queries) {
this.queries = {};
this.callbacks = [];
Object.entries(queries).forEach(([name, query]) => {
this.queries[name] = window.matchMedia(query);
this.queries[name].addEventListener('change', () => this.notify());
});
}
matches(name) {
return this.queries[name]?.matches || false;
}
onBreakpointChange(callback) {
this.callbacks.push(callback);
callback(this.getState());
}
getState() {
const state = {};
Object.keys(this.queries).forEach(name => {
state[name] = this.queries[name].matches;
});
return state;
}
notify() {
this.callbacks.forEach(callback => callback(this.getState()));
}
}
// Utilisation
const mediaManager = new MediaQueryManager({
mobile: '(max-width: 768px)',
tablet: '(min-width: 769px) and (max-width: 1024px)',
desktop: '(min-width: 1025px)'
});
mediaManager.onBreakpointChange((state) => {
console.log('État des breakpoints:', state);
if (state.mobile) {
document.body.classList.add('mobile');
} else {
document.body.classList.remove('mobile');
}
});Bonnes pratiques
1. Nettoyer les écouteurs
const mediaQuery = window.matchMedia('(max-width: 768px)');
function handleChange(e) {
// Votre code
}
mediaQuery.addEventListener('change', handleChange);
// Plus tard, pour nettoyer
mediaQuery.removeEventListener('change', handleChange);2. Vérifier immédiatement
Toujours exécuter la fonction de callback immédiatement pour initialiser l'état :
const isMobile = window.matchMedia('(max-width: 768px)');
function handleChange(e) {
// Code
}
isMobile.addEventListener('change', handleChange);
handleChange(isMobile); // ← Important !3. Utiliser des variables pour les breakpoints
const BREAKPOINTS = {
mobile: '(max-width: 768px)',
tablet: '(min-width: 769px) and (max-width: 1024px)',
desktop: '(min-width: 1025px)'
};
const isMobile = window.matchMedia(BREAKPOINTS.mobile);4. Éviter les recalculs inutiles
Utilisez un debounce si nécessaire :
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const isMobile = window.matchMedia('(max-width: 768px)');
const debouncedHandler = debounce((e) => {
// Code qui ne doit pas s'exécuter trop souvent
}, 250);
isMobile.addEventListener('change', debouncedHandler);Dépannage
La media query ne se déclenche pas
- Vérifiez la syntaxe de la media query
- Vérifiez que
addEventListenerest bien appelé - Vérifiez que la fonction de callback est bien exécutée immédiatement
Performance
Si vous avez beaucoup de media queries, regroupez-les :
// ❌ Mauvais : trop d'écouteurs
const mq1 = window.matchMedia('(max-width: 768px)');
const mq2 = window.matchMedia('(max-width: 768px)');
const mq3 = window.matchMedia('(max-width: 768px)');
// ✅ Bon : réutiliser la même instance
const mobileQuery = window.matchMedia('(max-width: 768px)');Compatibilité navigateurs
window.matchMedia() est supporté par tous les navigateurs modernes :
- ✅ Chrome 9+
- ✅ Firefox 6+
- ✅ Safari 5.1+
- ✅ Edge 12+
- ✅ iOS Safari 5.1+
Pour les anciens navigateurs, utilisez un polyfill ou une bibliothèque comme enquire.js.
Alternatives
Utiliser CSS avec des classes
Au lieu de JavaScript, vous pouvez utiliser CSS pour détecter les changements :
.mobile-only {
display: block;
}
@media (min-width: 769px) {
.mobile-only {
display: none;
}
}Puis en JavaScript, vérifier simplement la classe :
const isMobile = document.querySelector('.mobile-only').offsetParent !== null;Bibliothèques tierces
- enquire.js : Bibliothèque légère pour les media queries
- react-responsive : Pour React
- use-media : Hook React pour media queries
Ressources
- MDN matchMedia : Documentation (opens in a new tab)
- Media Queries : Spécification CSS (opens in a new tab)
- Responsive Design : Best Practices (opens in a new tab)