Techniques HTML5 permettant d'optimiser les performances sur mobile

Introduction

Dans les environnements Web mobile actuels, les actualisations lentes, les transitions de page saccadées et les retards périodiques ne sont que quelques exemples des problèmes que vous pouvez rencontrer dans les environnements Web mobile actuels. Les développeurs s'efforcent de se rapprocher le plus possible du format natif, mais ils sont souvent entravés par des piratages, des réinitialisations et des frameworks rigides.

Dans cet article, nous allons examiner le strict minimum nécessaire pour créer une application Web HTML5 mobile. L'objectif principal est de démasquer les complexités cachées que les frameworks mobiles d'aujourd'hui tentent de dissimuler. Vous découvrirez une approche minimaliste (à l'aide des API HTML5 de base) et des principes de base qui vous permettront d'écrire votre propre framework ou de contribuer à celui que vous utilisez actuellement.

Accélération matérielle

Normalement, les GPU gèrent la modélisation 3D détaillée ou les diagrammes CAO, mais dans ce cas, nous voulons que nos dessins primitifs (divs, arrière-plans, texte avec ombres portées, images, etc.) s'affichent et s'animent de manière fluide via le GPU. Malheureusement, la plupart des développeurs front-end transmettent ce processus d'animation à un framework tiers sans se soucier de la sémantique. Mais ces fonctionnalités CSS3 principales devraient-elles être masquées ? Voici quelques-unes des raisons pour lesquelles il est important de s'y intéresser:

  1. Allocation de mémoire et charge de calcul : si vous composez chaque élément du DOM uniquement pour l'accélération matérielle, la personne suivante qui travaillera sur votre code risque de vous poursuivre et de vous battre sévèrement.

  2. Consommation d'énergie : bien évidemment, lorsque le matériel entre en jeu, il en va de même pour la batterie. Lorsque les développeurs créent des applications Web mobiles, ils doivent tenir compte de la grande variété de contraintes liées aux appareils. Cela sera encore plus courant à mesure que les fabricants de navigateurs commenceront à autoriser l'accès à davantage de matériel d'appareil.

  3. Conflits – J'ai rencontré un comportement glitchs lorsque j'applique l'accélération matérielle à des parties de la page qui étaient déjà accélérées. Il est donc très important de savoir si l'accélération se chevauche.

Pour que l'interaction utilisateur soit fluide et aussi proche que possible de l'expérience native, nous devons faire en sorte que le navigateur nous aide. Idéalement, nous souhaitons que le processeur de l'appareil mobile configure l'animation initiale, puis que le GPU ne soit chargé que de composer les différentes couches pendant le processus d'animation. C'est ce que font translate3d, scale3d et translateZ : ils donnent aux éléments animés leur propre couche, ce qui permet à l'appareil d'afficher tout ensemble de manière fluide. Pour en savoir plus sur la composition accélérée et le fonctionnement de WebKit, Ariya Hidayat dispose de nombreuses informations intéressantes sur son blog.

Transitions de page

Examinons trois des approches d'interaction utilisateur les plus courantes lors du développement d'une application Web mobile: les effets de diapositive, de retournement et de rotation.

Vous pouvez voir ce code en action sur http://slidfast.appspot.com/slide-flip-rotate.html (remarque : cette démonstration est conçue pour un appareil mobile. Lancez un émulateur, utilisez votre téléphone ou votre tablette, ou réduisez la taille de la fenêtre de votre navigateur à environ 1 024 pixels ou moins).

Nous allons d'abord examiner les transitions de glissement, de retournement et de rotation, et la façon dont elles sont accélérées. Notez que chaque animation n'accepte que trois ou quatre lignes de CSS et de JavaScript.

Glissant

La plus courante des trois approches de transition : les transitions de page glissante imitent l'apparence native des applications mobiles. La transition de diapositive est appelée pour insérer une nouvelle zone de contenu dans le port de vue.

Pour l'effet de glissement, nous devons d'abord déclarer notre balisage:

<div id="home-page" class="page">
  <h1>Home Page</h1>
</div>

<div id="products-page" class="page stage-right">
  <h1>Products Page</h1>
</div>

<div id="about-page" class="page stage-left">
  <h1>About Page</h1>
</div>

Notez que nous avons ce concept de mise en scène de pages à gauche ou à droite. Il peut s'agir de n'importe quelle direction, mais celle-ci est la plus courante.

Nous avons maintenant une animation et une accélération matérielle avec seulement quelques lignes de CSS. L'animation réelle se produit lorsque nous échangeons des classes sur les éléments div de la page.

.page {
  position: absolute;
  width: 100%;
  height: 100%;
  /*activate the GPU for compositing each page */
  -webkit-transform: translate3d(0, 0, 0);
}

translate3d(0,0,0) est connu sous le nom de solution miracle.

Lorsque l'utilisateur clique sur un élément de navigation, nous exécutons le code JavaScript suivant pour permuter les classes. Aucun framework tiers n'est utilisé. Il s'agit de pur JavaScript. ;)

function getElement(id) {
  return document.getElementById(id);
}

function slideTo(id) {
  //1.) the page we are bringing into focus dictates how
  // the current page will exit. So let's see what classes
  // our incoming page is using. We know it will have stage[right|left|etc...]
  var classes = getElement(id).className.split(' ');

  //2.) decide if the incoming page is assigned to right or left
  // (-1 if no match)
  var stageType = classes.indexOf('stage-left');

  //3.) on initial page load focusPage is null, so we need
  // to set the default page which we're currently seeing.
  if (FOCUS_PAGE == null) {
    // use home page
    FOCUS_PAGE = getElement('home-page');
  }

  //4.) decide how this focused page should exit.
  if (stageType > 0) {
    FOCUS_PAGE.className = 'page transition stage-right';
  } else {
    FOCUS_PAGE.className = 'page transition stage-left';
  }

  //5. refresh/set the global variable
  FOCUS_PAGE = getElement(id);

  //6. Bring in the new page.
  FOCUS_PAGE.className = 'page transition stage-center';
}

stage-left ou stage-right devient stage-center et force la page à glisser dans le port de vue central. Nous dépendons entièrement du CSS3 pour effectuer le gros du travail.

.stage-left {
  left: -480px;
}

.stage-right {
  left: 480px;
}

.stage-center {
  top: 0;
  left: 0;
}

Examinons ensuite le CSS qui gère la détection et l'orientation des appareils mobiles. Nous pourrions prendre en charge tous les appareils et toutes les résolutions (voir la résolution des requêtes multimédias). Dans cette démonstration, je n'ai utilisé que quelques exemples simples pour couvrir la plupart des vues en mode portrait et paysage sur les appareils mobiles. Cela est également utile pour appliquer l'accélération matérielle par appareil. Par exemple, étant donné que la version pour ordinateur de bureau de WebKit accélère tous les éléments transformés (qu'ils soient en 2D ou en 3D), il est logique de créer une requête multimédia et d'exclure l'accélération à ce niveau. Notez que les astuces d'accélération matérielle n'apportent aucune amélioration de la vitesse sous Android Froyo 2.2 ou version ultérieure. Toute la composition est effectuée dans le logiciel.

/* iOS/android phone landscape screen width*/
@media screen and (max-device-width: 480px) and (orientation:landscape) {
  .stage-left {
    left: -480px;
  }

  .stage-right {
    left: 480px;
  }

  .page {
    width: 480px;
  }
}

Retournement

Sur les appareils mobiles, le fait de retourner une page revient à balayer l'écran. Ici, nous utilisons du code JavaScript simple pour gérer cet événement sur les appareils iOS et Android (basés sur WebKit).

Pour la voir en action, accédez à http://slidfast.appspot.com/slide-flip-rotate.html.

Lorsque vous travaillez avec des événements tactiles et des transitions, la première chose à faire est de déterminer la position actuelle de l'élément. Pour en savoir plus sur WebKitCSSMatrix, consultez cette documentation.

function pageMove(event) {
  // get position after transform
  var curTransform = new WebKitCSSMatrix(window.getComputedStyle(page).webkitTransform);
  var pagePosition = curTransform.m41;
}

Étant donné que nous utilisons une transition CSS3 de type "ease-out" pour le retournement de page, element.offsetLeft ne fonctionnera pas.

Nous devons ensuite déterminer dans quelle direction l'utilisateur fait pivoter l'écran et définir un seuil pour qu'un événement (navigation sur la page) se produise.

if (pagePosition >= 0) {
 //moving current page to the right
 //so means we're flipping backwards
   if ((pagePosition > pageFlipThreshold) || (swipeTime < swipeThreshold)) {
     //user wants to go backward
     slideDirection = 'right';
   } else {
     slideDirection = null;
   }
} else {
  //current page is sliding to the left
  if ((swipeTime < swipeThreshold) || (pagePosition < pageFlipThreshold)) {
    //user wants to go forward
    slideDirection = 'left';
  } else {
    slideDirection = null;
  }
}

Vous remarquerez également que nous mesurons également le swipeTime en millisecondes. Cela permet de déclencher l'événement de navigation si l'utilisateur balaie rapidement l'écran pour tourner une page.

Pour positionner la page et donner aux animations un aspect natif lorsqu'un doigt touche l'écran, nous utilisons des transitions CSS3 après chaque déclenchement d'événement.

function positionPage(end) {
  page.style.webkitTransform = 'translate3d('+ currentPos + 'px, 0, 0)';
  if (end) {
    page.style.WebkitTransition = 'all .4s ease-out';
    //page.style.WebkitTransition = 'all .4s cubic-bezier(0,.58,.58,1)'
  } else {
    page.style.WebkitTransition = 'all .2s ease-out';
  }
  page.style.WebkitUserSelect = 'none';
}

J'ai essayé de jouer avec cubic-bezier pour donner une sensation native optimale aux transitions, mais ease-out a fait l'affaire.

Enfin, pour que la navigation se produise, nous devons appeler les méthodes slideTo() précédemment définies que nous avons utilisées dans la dernière démonstration.

track.ontouchend = function(event) {
  pageMove(event);
  if (slideDirection == 'left') {
    slideTo('products-page');
  } else if (slideDirection == 'right') {
    slideTo('home-page');
  }
}

Rotation

Examinons maintenant l'animation de rotation utilisée dans cette démonstration. Vous pouvez à tout moment faire pivoter la page que vous consultez de 180 degrés pour révéler l'inverse en appuyant sur l'option de menu "Contact". Là encore, quelques lignes de CSS et du code JavaScript sont nécessaires pour attribuer une classe de transition onclick. REMARQUE: La transition "Faire pivoter" ne s'affiche pas correctement sur la plupart des versions d'Android, car il manque des fonctionnalités de transformation CSS 3D. Malheureusement, au lieu d'ignorer la partie pliable, Android fait éloigner la page en la faisant pivoter au lieu de la retourner. Nous vous recommandons d'utiliser cette transition avec parcimonie jusqu'à ce que la prise en charge s'améliore.

Le balisage (concept de base de l'avant et de l'arrière) :

<div id="front" class="normal">
...
</div>
<div id="back" class="flipped">
    <div id="contact-page" class="page">
        <h1>Contact Page</h1>
    </div>
</div>

Le code JavaScript:

function flip(id) {
  // get a handle on the flippable region
  var front = getElement('front');
  var back = getElement('back');

  // again, just a simple way to see what the state is
  var classes = front.className.split(' ');
  var flipped = classes.indexOf('flipped');

  if (flipped >= 0) {
    // already flipped, so return to original
    front.className = 'normal';
    back.className = 'flipped';
    FLIPPED = false;
  } else {
    // do the flip
    front.className = 'flipped';
    back.className = 'normal';
    FLIPPED = true;
  }
}

Le CSS :

/*----------------------------flip transition */
#back,
#front {
  position: absolute;
  width: 100%;
  height: 100%;
  -webkit-backface-visibility: hidden;
  -webkit-transition-duration: .5s;
  -webkit-transform-style: preserve-3d;
}

.normal {
  -webkit-transform: rotateY(0deg);
}

.flipped {
  -webkit-user-select: element;
  -webkit-transform: rotateY(180deg);
}

Déboguer l'accélération matérielle

Maintenant que nous avons abordé nos transitions de base, examinons la mécanique qui détermine leur fonctionnement et leur composition.

Pour que cette session de débogage magique se produise, démarrons quelques navigateurs et votre IDE de choix. Commencez par démarrer Safari à partir de la ligne de commande pour utiliser certaines variables d'environnement de débogage. Je suis sur Mac, les commandes peuvent donc varier selon votre OS. Ouvrez le terminal et saisissez la commande suivante:

  • $> export CA_COLOR_OPAQUE=1
  • $> exporter CA_LOG_MEMORY_USAGE=1
  • $> /Applications/Safari.app/Contents/MacOS/Safari

Safari est lancé avec quelques assistants de débogage. CA_COLOR_OPAQUE indique les éléments qui sont réellement composés ou accélérés. CA_LOG_MEMORY_USAGE nous indique la quantité de mémoire utilisée lors de l'envoi de nos opérations de dessin au magasin de stockage. Cette métrique vous indique exactement la charge que vous exercez sur l'appareil mobile. Elle peut également vous donner des indications sur la façon dont l'utilisation du GPU peut décharger la batterie de l'appareil cible.

Lancez maintenant Chrome pour obtenir des informations sur la fréquence d'images par seconde (FPS) :

  1. Ouvrez le navigateur Web Google Chrome.
  2. Dans la barre d'URL, saisissez about:flags.
  3. Faites défiler la page vers le bas et cliquez sur "Enable" (Activer) pour le paramètre "FP Counter" (Compteur d'images par seconde).

Si vous consultez cette page dans votre version optimisée de Chrome, le compteur FPS rouge s'affiche en haut à gauche.

FPS Chrome

C'est ainsi que nous savons que l'accélération matérielle est activée. Cela nous donne également une idée de la façon dont l'animation s'exécute et si vous avez des fuites (animations en cours d'exécution continue qui doivent être arrêtées).

Une autre façon de visualiser l'accélération matérielle consiste à ouvrir la même page dans Safari (avec les variables d'environnement que j'ai mentionnées ci-dessus). Chaque élément DOM accéléré est associé à une teinte rouge. Cela nous montre exactement ce qui est composite par calque. Notez que la navigation blanche n'est pas rouge, car elle n'est pas accélérée.

Contact composite

Un paramètre similaire pour Chrome est également disponible dans about:flags "Bordures de la couche de rendu composite".

Une autre bonne façon de voir les couches composées est de regarder la démonstration "Falling Sheets" de WebKit lorsque ce mod est appliqué.

Feuilles composées

Enfin, pour bien comprendre les performances matérielles graphiques de notre application, examinons la consommation de mémoire. Dans cet exemple, nous envoyons 1,38 Mo d'instructions de dessin aux tampons CoreAnimation sous Mac OS. Les tampons de mémoire Core Animation sont partagés entre OpenGL ES et le GPU pour créer les pixels finaux que vous voyez à l'écran.

Coreanimation 1

Lorsque nous redimensionnons ou maximisons simplement la fenêtre du navigateur, la mémoire augmente également.

Coreanimation 2

Cela vous donne une idée de la façon dont la mémoire est consommée sur votre appareil mobile, mais uniquement si vous redimensionnez le navigateur aux dimensions appropriées. Si vous effectuiez un débogage ou des tests pour des environnements iPhone, redimensionnez l'écran à 480 x 320 pixels. Nous comprenons maintenant exactement comment fonctionne l'accélération matérielle et ce qu'il faut pour déboguer. Il est une chose de lire à ce sujet, mais voir les tampons de mémoire du GPU fonctionner visuellement permet de mieux comprendre.

En coulisses : récupération et mise en cache

Il est maintenant temps de passer à la vitesse supérieure pour la mise en cache des pages et des ressources. À l'instar de l'approche utilisée par JQuery Mobile et d'autres frameworks similaires, nous allons effectuer le préchargement et la mise en cache de nos pages avec des appels AJAX simultanés.

Abordons quelques problèmes essentiels du Web pour mobile et les raisons pour lesquelles nous devons y remédier:

  • Récupération: le préchargement de nos pages permet aux utilisateurs de passer l'application en mode hors connexion et de ne pas attendre entre les actions de navigation. Bien entendu, nous ne voulons pas limiter la bande passante de l'appareil lorsqu'il se connecte. Nous devons donc utiliser cette fonctionnalité avec parcimonie.
  • Mise en cache: nous voulons ensuite une approche simultanée ou asynchrone lors de la récupération et de la mise en cache de ces pages. Nous devons également utiliser localStorage (car il est bien compatible avec les appareils), qui n'est malheureusement pas asynchrone.
  • AJAX et analyse de la réponse: utiliser innerHTML() pour insérer la réponse AJAX dans le DOM est dangereux (et peu fiable ?). Nous utilisons plutôt un mécanisme fiable pour l'insertion des réponses AJAX et la gestion des appels simultanés. Nous exploitons également certaines nouvelles fonctionnalités de HTML5 pour analyser le xhr.responseText.

En nous appuyant sur le code de la démo de diapositives, de retournements et de rotations, nous commençons par ajouter des pages secondaires et les associer. Nous analyserons ensuite les liens et créerons des transitions instantanément.

Écran d&#39;accueil de l&#39;iPhone

Découvrez la démonstration de la récupération et de la mise en cache.

Comme vous pouvez le constater, nous utilisons ici le balisage sémantique. Il s'agit simplement d'un lien vers une autre page. La page enfant suit la même structure de nœuds/classes que sa page parente. Nous pourrions aller un peu plus loin et utiliser l'attribut data-* pour les nœuds "page", etc. Voici la page d'informations (enfant) située dans un fichier HTML distinct (/demo2/home-detail.html) qui sera chargé, mis en cache et configuré pour la transition lors du chargement de l'application.

<div id="home-page" class="page">
  <h1>Home Page</h1>
  <a href="demo2/home-detail.html" class="fetch">Find out more about the home page!</a>
</div>

Voyons maintenant le code JavaScript. Par souci de simplicité, je n'inclus aucune aide ni aucune optimisation dans le code. Tout ce que nous faisons ici est d'effectuer une boucle sur un tableau spécifié de nœuds DOM pour extraire les liens à extraire et mettre en cache. Remarque : Pour cette démonstration, la méthode fetchAndCache() est appelée au chargement de la page. Nous le retravaillerons dans la section suivante lorsque nous détecterons la connexion réseau et déterminerons quand il doit être appelé.

var fetchAndCache = function() {
  // iterate through all nodes in this DOM to find all mobile pages we care about
  var pages = document.getElementsByClassName('page');

  for (var i = 0; i < pages.length; i++) {
    // find all links
    var pageLinks = pages[i].getElementsByTagName('a');

    for (var j = 0; j < pageLinks.length; j++) {
      var link = pageLinks[j];

      if (link.hasAttribute('href') &amp;&amp;
      //'#' in the href tells us that this page is already loaded in the DOM - and
      // that it links to a mobile transition/page
         !(/[\#]/g).test(link.href) &amp;&amp;
        //check for an explicit class name setting to fetch this link
        (link.className.indexOf('fetch') >= 0))  {
         //fetch each url concurrently
         var ai = new ajax(link,function(text,url){
              //insert the new mobile page into the DOM
             insertPages(text,url);
         });
         ai.doGet();
      }
    }
  }
};

Nous garantissons un post-traitement asynchrone approprié grâce à l'utilisation de l'objet "AJAX". Pour une explication plus détaillée de l'utilisation de localStorage dans un appel AJAX, consultez Travailler hors connexion avec HTML5 hors connexion. Dans cet exemple, vous voyez l'utilisation de base du cache pour chaque requête et la fourniture des objets mis en cache lorsque le serveur renvoie autre chose qu'une réponse réussie (200).

function processRequest () {
  if (req.readyState == 4) {
    if (req.status == 200) {
      if (supports_local_storage()) {
        localStorage[url] = req.responseText;
      }
      if (callback) callback(req.responseText,url);
    } else {
      // There is an error of some kind, use our cached copy (if available).
      if (!!localStorage[url]) {
        // We have some data cached, return that to the callback.
        callback(localStorage[url],url);
        return;
      }
    }
  }
}

Malheureusement, comme localStorage utilise la norme UTF-16 pour l'encodage des caractères, chaque octet est stocké sous forme de 2 octets, ce qui fait passer notre limite de stockage de 5 Mo à 2,6 Mo au total. La raison pour laquelle ces pages/éléments de balisage sont extraits et mis en cache en dehors du champ d'application du cache est expliquée dans la section suivante.

Grâce aux avancées récentes de l'élément iframe avec HTML5, nous disposons désormais d'un moyen simple et efficace d'analyser le responseText renvoyé par notre appel AJAX. Il existe de nombreux analyseurs JavaScript de 3 000 lignes et des expressions régulières qui suppriment les balises de script, etc. Mais pourquoi ne pas laisser le navigateur faire ce qu'il fait de mieux ? Dans cet exemple, nous allons écrire le responseText dans une iframe temporaire masquée. Nous utilisons l'attribut "bac à sable" HTML5, qui désactive les scripts et offre de nombreuses fonctionnalités de sécurité.

À partir de la spécification : Lorsqu'il est spécifié, l'attribut sandbox active un ensemble de restrictions supplémentaires sur tout contenu hébergé par l'iFrame. Sa valeur doit être un ensemble non ordonné de jetons uniques séparés par des espaces, qui sont sensibles à la casse ASCII. Les valeurs autorisées sont allow-forms, allow-same-origin, allow-scripts et allow-top-navigation. Lorsque l'attribut est défini, le contenu est considéré comme provenant d'une origine unique. Les formulaires et les scripts sont désactivés, les liens ne peuvent pas cibler d'autres contextes de navigation et les plug-ins sont désactivés.

var insertPages = function(text, originalLink) {
  var frame = getFrame();
  //write the ajax response text to the frame and let
  //the browser do the work
  frame.write(text);

  //now we have a DOM to work with
  var incomingPages = frame.getElementsByClassName('page');

  var pageCount = incomingPages.length;
  for (var i = 0; i < pageCount; i++) {
    //the new page will always be at index 0 because
    //the last one just got popped off the stack with appendChild (below)
    var newPage = incomingPages[0];

    //stage the new pages to the left by default
    newPage.className = 'page stage-left';

    //find out where to insert
    var location = newPage.parentNode.id == 'back' ? 'back' : 'front';

    try {
      // mobile safari will not allow nodes to be transferred from one DOM to another so
      // we must use adoptNode()
      document.getElementById(location).appendChild(document.adoptNode(newPage));
    } catch(e) {
      // todo graceful degradation?
    }
  }
};

Safari refuse correctement de déplacer implicitement un nœud d'un document à un autre. Une erreur est générée si le nouveau nœud enfant a été créé dans un autre document. Ici, nous utilisons adoptNode et tout va bien.

Pourquoi utiliser un iFrame ? Pourquoi ne pas simplement utiliser innerHTML ? Même si innerHTML fait désormais partie de la spécification HTML5, il est dangereux d'insérer la réponse d'un serveur (malveillante ou non) dans une zone non validée. Au moment de rédiger cet article, je n'ai trouvé personne n'utilisant que innerHTML. Je sais que JQuery l'utilise à la base avec un remplacement d'ajout en cas d'exception uniquement. jQuery Mobile l'utilise également. Toutefois, je n'ai pas effectué de tests approfondis concernant le fait que innerHTML "cesse de fonctionner de manière aléatoire", mais il serait très intéressant de voir toutes les plates-formes concernées. Il serait également intéressant de voir quelle approche est la plus performante... J'ai également reçu des affirmations des deux parties à ce sujet.

Détection, gestion et profilage du type de réseau

Maintenant que nous pouvons mettre en mémoire tampon (ou en cache prédictif) notre application Web, nous devons fournir les fonctionnalités de détection de connexion appropriées qui rendent notre application plus intelligente. C'est là que le développement d'applications mobiles devient extrêmement sensible aux modes en ligne/hors connexion et à la vitesse de connexion. Saisissez API Network Information. Chaque fois que je présente cette fonctionnalité dans une présentation, une personne du public lève la main et me demande "Dans quel but ?". Voici donc un moyen de configurer une application Web mobile extrêmement intelligente.

Commençons par un scénario de bon sens ennuyeux… Lorsque vous interagissez avec le Web depuis un appareil mobile dans un train à grande vitesse, le réseau peut très bien disparaître à différents moments et les différentes zones géographiques peuvent prendre en charge différentes vitesses de transmission (par exemple, HSPA ou 3G peuvent être disponibles dans certaines zones urbaines, mais les zones éloignées peuvent prendre en charge des technologies 2G beaucoup plus lentes.) Le code suivant couvre la plupart des scénarios de connexion.

Le code suivant fournit :

  • Accès hors connexion via applicationCache.
  • Détecte si l'article est ajouté aux favoris et hors connexion.
  • Détecte le passage de l'état hors connexion à l'état en ligne et vice-versa.
  • Détecte les connexions lentes et extrait le contenu en fonction du type de réseau.

Là encore, toutes ces fonctionnalités nécessitent très peu de code. Nous détectons d'abord nos événements et nos scénarios de chargement :

window.addEventListener('load', function(e) {
 if (navigator.onLine) {
  // new page load
  processOnline();
 } else {
   // the app is probably already cached and (maybe) bookmarked...
   processOffline();
 }
}, false);

window.addEventListener("offline", function(e) {
  // we just lost our connection and entered offline mode, disable eternal link
  processOffline(e.type);
}, false);

window.addEventListener("online", function(e) {
  // just came back online, enable links
  processOnline(e.type);
}, false);

Dans les EventListeners ci-dessus, nous devons indiquer à notre code s'il est appelé à partir d'un événement, d'une requête ou d'une actualisation de page. La raison principale est que l'événement onload du corps ne se déclenche pas lors du passage du mode en ligne au mode hors connexion.

Ensuite, nous effectuons une vérification simple d'un événement ononline ou onload. Ce code réinitialise les liens désactivés lorsque vous passez de l'état hors connexion à l'état en ligne. Toutefois, si cette application était plus sophistiquée, vous pourriez insérer une logique qui reprendrait l'extraction de contenu ou gérerait l'expérience utilisateur pour les connexions intermittentes.

function processOnline(eventType) {

  setupApp();
  checkAppCache();

  // reset our once disabled offline links
  if (eventType) {
    for (var i = 0; i < disabledLinks.length; i++) {
      disabledLinks[i].onclick = null;
    }
  }
}

Il en va de même pour processOffline(). Dans ce cas, vous devez manipuler votre application pour le mode hors connexion et essayer de récupérer toutes les transactions effectuées en arrière-plan. Le code ci-dessous recherche tous nos liens externes et les désactive, piégeant les utilisateurs dans notre application hors connexion POUR TOUJOURS, muhahaha !

function processOffline() {
  setupApp();

  // disable external links until we come back - setting the bounds of app
  disabledLinks = getUnconvertedLinks(document);

  // helper for onlcick below
  var onclickHelper = function(e) {
    return function(f) {
      alert('This app is currently offline and cannot access the hotness');return false;
    }
  };

  for (var i = 0; i < disabledLinks.length; i++) {
    if (disabledLinks[i].onclick == null) {
      //alert user we're not online
      disabledLinks[i].onclick = onclickHelper(disabledLinks[i].href);

    }
  }
}

OK, passons aux choses sérieuses. Maintenant que notre application sait dans quel état de connexion elle se trouve, nous pouvons également vérifier le type de connexion lorsqu'elle est en ligne et l'ajuster en conséquence. J'ai indiqué les vitesses de téléchargement et les latences typiques des fournisseurs nord-américains dans les commentaires de chaque connexion.

function setupApp(){
  // create a custom object if navigator.connection isn't available
  var connection = navigator.connection || {'type':'0'};
  if (connection.type == 2 || connection.type == 1) {
      //wifi/ethernet
      //Coffee Wifi latency: ~75ms-200ms
      //Home Wifi latency: ~25-35ms
      //Coffee Wifi DL speed: ~550kbps-650kbps
      //Home Wifi DL speed: ~1000kbps-2000kbps
      fetchAndCache(true);
  } else if (connection.type == 3) {
  //edge
      //ATT Edge latency: ~400-600ms
      //ATT Edge DL speed: ~2-10kbps
      fetchAndCache(false);
  } else if (connection.type == 2) {
      //3g
      //ATT 3G latency: ~400ms
      //Verizon 3G latency: ~150-250ms
      //ATT 3G DL speed: ~60-100kbps
      //Verizon 3G DL speed: ~20-70kbps
      fetchAndCache(false);
  } else {
  //unknown
      fetchAndCache(true);
  }
}

Nous pourrions apporter de nombreux ajustements à notre processus fetchAndCache, mais tout ce que j'ai fait ici a été de lui demander de récupérer les ressources de manière asynchrone (true) ou synchrone (false) pour une connexion donnée.

Chronologie des requêtes Edge (synchrones)

Edge Sync

WIFI (Asynchronous) Request Timeline

WIFI Async

Cela permet d'ajuster au moins une partie de l'expérience utilisateur en fonction des connexions lentes ou rapides. Il ne s'agit en aucun cas d'une solution tout-en-un. Une autre tâche consiste à afficher une fenêtre modale de chargement lorsqu'un utilisateur clique sur un lien (sur des connexions lentes) alors que l'application peut toujours récupérer la page de ce lien en arrière-plan. L'essentiel est de réduire les temps d'attente tout en tirant pleinement parti de la connexion de l'utilisateur avec les toutes dernières fonctionnalités HTML5 les plus performantes. Découvrez la démonstration de la détection de réseau.

Conclusion

L'évolution des applications HTML5 pour mobile ne fait que commencer. Vous pouvez maintenant voir les fondements très simples d'un "cadre" pour mobile basé uniquement sur HTML5 et sur des technologies compatibles. Je pense qu'il est important que les développeurs travaillent avec ces fonctionnalités et les traitent dans leur essence, et non masquées par un wrapper.