Détecter la taille du device en JS via CSS

Avez-vous déjà eu le cas d’un script qui doit s’exécuter de manière différente selon la taille de l’écran ? Comment ne pas dupliquer les tailles des media-queries dans le fichier javascript ?

Des réponses sont apportées avec cette technique, issue de cet article Device State Detection with CSS Media Queries and JavaScript.

Ce que j’aime ici c’est surtout utiliser le fichier css, où les media queries sont déjà insérées, comme d’un fichier de config pour le javascript.

J’ai déjà vu dans certains projets, l’utilisation de modernizr pour exécuter une partie de code seulement lorsque la taille de l’écran correspond à la media query, comme ceci : Modernizr.mq('only all and (max-width: 400px)')

Je ne trouve pas ça terrible, car nous devons dupliquer nos media queries dans fichier CSS, et le fichier javascript. Beaucoup plus difficile à maintenir.

Media queries

Pour chaque media queries du site on rajoute une classe .state-indicator

/* default state */
.state-indicator {
    position: absolute;
    top: -999em;
    left: -999em;
    z-index: 1;
}
.state-indicator:before { content: 'desktop'; }
/* small desktop */
@media all and (max-width: 1200px) {
    .state-indicator { z-index: 2; }
    .state-indicator:before { content: 'small-desktop'; }
}
/* tablet */
@media all and (max-width: 1024px) {
    .state-indicator { z-index: 3; }
    .state-indicator:before { content: 'tablet'; }
}
/* mobile phone */
@media all and (max-width: 768px) {
    .state-indicator { z-index: 4; }
    .state-indicator:before { content: 'mobile'; }
}

Avec ça on définit un état de notre élément .state-indicator pour chacune de nos media queries. Cet élément sera positionner off-screen, pour qu’il n’interfère pas avec les autres éléments du site. Maintenant que nous avons défini cette classe .state-indicator, il va falloir insérer l’élément dans le DOM.

Javascript

// On crée l'élément et on l'insère
var indicator = document.createElement('div');
indicator.className = 'state-indicator';
document.body.appendChild(indicator);

// Une methode pour retourner la chaine de caractère qui est définie dans `.state-indicator:before`
function getDeviceState() {
    return window.getComputedStyle( 
        document.querySelector('.state-indicator'), ':before'
    ).getPropertyValue('content');
}

Ainsi, on peut simplement faire

if( getDeviceState() == 'tablet') { // Code executé pour les tablettes uniquement }

Avec ça, pas de calcul de taille en js, on laisse le navigateur gérer ça pour nous, et on récupère la chaine de caractère qui nous indique l’état du device. D’accord, mais si l’utilisateur redimensionne la fenêtre ? Alors il suffit de mettre notre fonction dans un resize event. Ce qui suit utilise, une fonction debounce pour performance et aussi une petite pub/sub librairie.

(function() {
var lastDeviceState = getDeviceState();
    window.addEventListener('resize', debounce(function() {
        var state = getDeviceState();
        if(state != lastDeviceState) {
            // Sauvegard du nouvel état
            lastDeviceState = state;

            // Publie le changement d'état, on pourrait aussi utiliser un évènement custom
            publish('/device-state/change', state);
        }
    }, 20));
})();
// Usage
subscribe('/device-state/change', function(state) {
    if(state == 'tablet') { 
        // Rentrera ici si la fenêtre est redimensionné, et que la taille correspond à une tabllette
    }
});

Voilà, une façon assez claire et propre de savoir à n’importe quel moment en quel est l’état du device. Pratique également, si selon la taille de l’écran on doit déclencher tel ou tel script. On peut aussi aller plus loin, en ajoutant d’autres états, comme le mode portrait ou paysage ou encore la détection de retina. Le javascript ne s’occupe à aucun moment de la détection (comme le fait modernizr), il laisse faire le CSS, et récupère le CSS computed par le navigateur. Les media queries sont alors gérés à un seul endroit, mais aussi accessible depuis le javascript, c’est là le vrai bonus de cette technique.

comments powered by Disqus