Comment utiliser les capteurs d'orientation en JavaScript grâce à Gyro.js

Apprenez dans cet article comment nous avons simplement utilisé le gyroscope et l’accéléromètre dans le navigateur pour le header du site JS-Republic (sous mobile bien sûr) grâce à Gyro.js.

Petit rappel

Repère tridimensionnel du téléphone
(source : http://www.alsacreations.com/tuto/lire/1501-api-device-orientation-motion-acceleration.html)
 
Avant de commencer les explications sur leur utilisation dans le navigateur, faisons une petite piqure de rappel sur ce que sont ces capteurs.
Votre mobile utilise le gyroscope pour connaitre son orientation dans l’espace, cette orientation est exprimé selon alpha, beta et gamma, trois angles de rotation en degrés autour des axes Z, X et Y. L’accéléromètre est utilisé pour mesurer les mouvements que fait votre téléphone en mètre/seconde² (mesure d’accélération). Combinés, ces deux capteurs peuvent donc mesurer les accélérations des rotations de l’appareil.
 
 

Cas (pas si) simple

En HTML5, il existe deux API pour récupérer les mesures de ces capteurs, la deviceorientation API et la devicemotion API , la première pour récupérer l’orientation du téléphone (alpha, beta, gamma) et la deuxième pour récupérer en plus les accélérations des mouvements. Dans un premier temps, on peut avoir toutes les informations nécessaires avec quelques lignes de JavaScript :

window.addEventListener('devicemotion', (eventData) => {
    console.log(`
        Angle Alpha : ${eventData.rotationRate.alpha}
        Angle Beta : ${eventData.rotationRate.beta}
        Angle Gamma : ${eventData.rotationRate.gamma}
        Accélération en X : ${eventData.accelerationIncludingGravity.x}
        Accélération en Y : ${eventData.accelerationIncludingGravity.Y}
        Accélération en Z : ${eventData.accelerationIncludingGravity.Z}
    `);
}, false);

Au vue de la simplicité de l’API, il ne parait donc pas nécessaire de faire appel à une quelconque librairie ! Et bien, ce n’est pas si simple car son utilisation demande quelques subtilités qui ne sont pas évidentes de prime abord.
La première et probablement la plus importante est le calibrage. En effet, l’état “neutre” des vos capteurs a lieu lorsque que le téléphone est immobile et posé à plat sur une surface horizontale. Or, vous utilisez la plupart du temps votre téléphone en portrait plus ou moins face à votre visage. Il faut donc procéder à un calibrage, pour considérer que la position “neutre” est celle qu’a le téléphone au chargement de la page et l’approche naïve serait de faire simplement une soustraction comme ceci :

let initialOrientation = null;
window.addEventListener('devicemotion', (eventData) => {
    const r = eventData.rotationRate;
    initialOrientation = initialOrientation || Object.assign({}, r); // first time
    console.log(`
        Angle calibré Alpha : ${r.alpha - initialOrientation.alpha }
        Angle calibré Beta : ${r.beta - initialOrientation.beta}
        Angle calibré Gamma : ${r.gamma -initialOrientation.gamma}
    `);
}, false);

Mais comme l’énonce @yvt dans son issue sur le projet Gyro.js, cette approche est erronée !
Il explique, que les angles d’Eulers utilisés pour donner l’orientation du téléphone ne peuvent pas être combinés quand ils agissent sur deux axes différents par une simple addition ou soustraction. Et que si l’on veut faire cette manipulation, une manière élégante de le faire est d’utiliser les Quaternions. Je vous laisse vous régaler avec l’implémentation du calibrage pour le moins mathématique : Merge de la pull request
Gyro.js gère également pour vous la détection de présence des API HTML5, et ajoute au dessus de celles-ci une API “pull” qui permet de récupérer les dernières mesures connues des capteurs à la demande. Chose bien pratique quand vous utilisez le requestAnimationFrame comme nous dans la suite.

Utilisation de Gyro.js

Gyro.js est plus un script  qu’une librairie, créé par Tom Gallacher, l’utilitaire ne comprend que quelques méthodes et propriétés :

  • gyro.frequency = 500 // périodicité à laquelle le script appel la callback d’event
  • gyro.getOrientation() // retourne les mesures courantes
  • gyro.calibrate() // calibre les mesures retournée
  • gyro.startTracking(function(o){…}) // démarre l’appel périodique à la callback d’event
  • gyro.stopTracking() // arrête l’appel à périodique à la callback d’event
  • gyro.hasFeature(‘devicemotion’) // vérifie si le navigateur supporte MozOrientation, devicemotion ou deviceorientation
  • gyro.getFeatures() // renvoie les API de ces capteurs supportées

Après différents essais, nous avons obtenu le meilleur rendu en n’utilisant pas l’appel périodique à callback proposé par l’API Gyro.js mais plutôt en passant par le requestAnimationFrame. Cela permet, d’associer un mouvement à l’orientation du téléphone de manière fluide. L’implémentation simplifiée de ce concept ressemble à ceci :

gyro.stopTracking(); // Stop periodic calls
gyro.calibrate(); // Calibrate measurement during the page loading
const speed = 0.01; // A arbitrary speed ...
let lastTimestamp = null;
const refresh = function(timestamp) {
    if (lastTimestamp !== null) { // execute only if timestamp is valued
        const delay = timestamp - lastTimestamp; // duration since the last call
        const o = gyro.getOrientation(); // retrieves the last measures
        const step = speed * delay;
        // Apply step on the background-position in our example, but
        // you can use directly the angles for a CSS rotation property
        // for instance.
    }
    lastTimestamp = timestamp; // save current timestamp for the next call
    window.requestAnimationFrame(refresh); // call for the next refresh (max 60 times per seconde)
};
window.requestAnimationFrame(refresh); // first call

Je vous recommande vivement d’arrondir les mesures retournées car elles changent sensiblement en permanence,
même quand le téléphone est immobile. Cela évitera les effets de tremblement ou de mouvement indésirables.

Le résultat final ressemble à ceci :
Header JS-Republic en mouvement sur mobile