Techniques HTML5 permettant d'optimiser les performances sur mobile

Wesley Hales
Wesley Hales

Introduction

Les actualisations tournantes, les transitions de page saccadées et les retards périodiques dans les événements tactiles ne sont que quelques-uns des problèmes des environnements Web mobiles d'aujourd'hui. Les développeurs essaient de se rapprocher le plus possible du format natif, mais ils sont souvent dévalorisés par des piratages, des réinitialisations et des frameworks rigides.

Dans cet article, nous allons aborder les étapes minimales requises pour créer une application Web HTML5 pour mobile. L'objectif principal est de révéler les complexités cachées que les frameworks pour mobile actuels tentent de dissimuler. Vous découvrirez une approche minimaliste (utilisant les principales API HTML5) et des principes de base qui vous permettront de créer 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 ou les diagrammes de CAO détaillés, mais dans ce cas, nous voulons que nos dessins primitifs (div, arrière-plans, texte avec ombres projetées, images, etc.) apparaissent et s'animent fluides via le GPU. Malheureusement, la plupart des développeurs front-end proposent ce processus d'animation à un framework tiers sans se soucier de la sémantique. Mais ces principales fonctionnalités CSS3 doivent-elles être masquées ? Voici quelques raisons pour lesquelles il est important de s'intéresser à ce sujet:

  1. Allocation de mémoire et charge de calcul : si vous composez tous les éléments du DOM pour des raisons d'accélération matérielle, la prochaine personne qui travaillera sur votre code risque de vous retarder et de vous battre de manière sévère.

  2. Consommation d'énergie : bien entendu, lorsque le matériel se met en marche, la batterie aussi. Lors du développement d'applications mobiles, les développeurs sont obligés de prendre en compte les nombreuses contraintes liées aux appareils lorsqu'ils écrivent des applications Web mobiles. Ce phénomène sera d'autant plus important que les fabricants de navigateurs commenceront à permettre l'accès à des appareils de plus en plus nombreux.

  3. Conflits : j'ai rencontré des problèmes lors de l'activation de l'accélération matérielle sur 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 du format natif, nous devons faire en sorte que le navigateur fonctionne pour nous. Dans l'idéal, le processeur de l'appareil mobile doit configurer l'animation initiale, puis le GPU doit se charger de la composition des différentes couches pendant le processus d'animation. C'est ce que font translation3d, scale3d et translationZ. Elles attribuent aux éléments animés leur propre couche, ce qui permet à l'appareil d'afficher les éléments ensemble de manière fluide. Pour en savoir plus sur la composition accélérée et le fonctionnement de WebKit, Ariya Hidayat contient 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 glissement, de retournement et de rotation.

Vous pouvez voir ce code en action ici 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).

Tout d'abord, nous allons détailler les transitions de glissement, de retournement et de rotation, ainsi que la façon dont elles sont accélérées. Notez que chaque animation ne prend que trois ou quatre lignes de CSS et de JavaScript.

Glissantes

La plus courante des trois approches de transition : les transitions de page glissantes imitent l'aspect natif des applications mobiles. La transition de diapositive est appelée pour faire apparaître une nouvelle zone de contenu dans la fenêtre d'affichage.

Pour l'effet de diapositive, nous déclarons d'abord 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>

Remarquez comment nous avons ce concept de préproduction des pages à gauche ou à droite. Il peut s'agir de n'importe quelle direction, mais c'est le plus courant.

Nous proposons désormais l'animation et l'accélération matérielle avec seulement quelques lignes de code CSS. L'animation se produit lorsque nous échangeons les classes au niveau des é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 connue sous le nom de "solution éclair".

Lorsque l'utilisateur clique sur un élément de navigation, nous exécutons le JavaScript suivant pour permuter les classes. Aucun framework tiers n'est utilisé. Il s'agit tout simplement de 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 la vue centrale. Nous dépendons complètement du CSS3 pour faire le gros du travail.

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

.stage-right {
  left: 480px;
}

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

Examinons maintenant le CSS qui gère la détection et l'orientation des appareils mobiles. Nous pouvions cibler chaque appareil et chaque résolution (voir Résolution des requêtes multimédias). J'ai utilisé quelques exemples simples dans cette démo pour couvrir la plupart des vues en mode portrait et paysage sur les appareils mobiles. Ceci est également utile pour appliquer l'accélération matérielle par appareil. Par exemple, étant donné que la version pour ordinateur de WebKit accélère tous les éléments transformés (qu'il s'agisse d'éléments en 2D ou 3D), il est judicieux de créer une requête média et d'exclure l'accélération à ce niveau. Notez que les astuces d'accélération matérielle ne permettent pas d'améliorer la vitesse sous Android Froyo 2.2+. Toute 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, on parle de balayage de la page. Ici, nous utilisons un simple code JavaScript pour gérer cet événement sur les appareils iOS et Android (basés sur WebKit).

Découvrez-le en action : http://slidfast.appspot.com/slide-flip-rotate.html.

Lorsque vous traitez des événements tactiles et des transitions, la première chose à faire est de connaître la position actuelle de l'élément. Consultez ce document pour plus d'informations sur WebKitCSSMatrix.

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 pour le retournement de page, l'élément element.offsetLeft habituel ne fonctionnera pas.

Ensuite, nous voulons déterminer dans quelle direction l'utilisateur retourne et définir un seuil pour qu'un événement (navigation sur les pages) 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 la swipeTime en millisecondes. Cela permet à l'événement de navigation de se déclencher si l'utilisateur balaie rapidement l'écran pour tourner une page.

Pour positionner la page et rendre les animations natives 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 la baguette cubique pour donner un rendu optimal aux transitions, mais le lissage s'est avéré efficace.

Enfin, pour que la navigation ait lieu, nous devons appeler les méthodes slideTo() définies précédemment 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émo. Vous pouvez à tout moment faire pivoter la page actuellement 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 une partie de 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 elle ne propose pas de fonctionnalités de transformation CSS 3D. Malheureusement, au lieu d'ignorer le retournement, Android fait pivoter la page au lieu de la faire pivoter. Nous vous recommandons d'utiliser cette transition avec parcimonie jusqu'à ce que la compatibilité s'améliore.

Le balisage (concept de base du recto et du verso):

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

Le 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 façon dont elles fonctionnent et sont composées.

Pour que cette session de débogage magique se produise, nous allons lancer quelques navigateurs et l'IDE de votre choix. Commencez par démarrer Safari à partir de la ligne de commande pour utiliser certaines variables d'environnement de débogage. J'utilise un Mac. Les commandes peuvent donc varier en fonction de votre système d'exploitation. Ouvrez le terminal et saisissez la commande suivante:

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

Cela permet de démarrer Safari avec quelques assistants de débogage. CA_COLOR_OPAQUE nous montre quels éléments sont réellement composés ou accélérés. CA_LOG_MEMORY_USAGE indique la quantité de mémoire que nous utilisons lors de l'envoi de nos opérations de dessin au magasin de stockage. Cela vous indique exactement la charge sur l'appareil mobile et éventuellement comment votre utilisation du GPU peut décharger la batterie de l'appareil cible.

À présent, lançons Chrome pour afficher des informations relatives au nombre de frames par seconde (FPS) :

  1. Ouvrez le navigateur Web Google Chrome.
  2. Dans la barre d'adresse, saisissez about:flags.
  3. Faites défiler la page vers le bas, puis cliquez sur "Activer" pour "Compteur de FPS".

Si vous consultez cette page dans la version améliorée de Chrome, vous verrez un compteur de FPS rouge dans l'angle supérieur gauche.

Lecteur d&#39;empreinte digitale Chrome

Cela nous permet de savoir 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 des éventuelles fuites (animations en cours d'exécution 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 mentionnées ci-dessus). Chaque élément DOM accéléré a une teinte rouge. Cela nous montre exactement ce qui est composé par couche. Notez que la barre de 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 des couches de rendu composées".

Un autre bon moyen d'afficher les couches composites consiste à visionner la démonstration de WebKit "Falling feuilles" pendant que ce mod est appliqué.

feuilles ompositées

Enfin, pour bien comprendre les performances graphiques matérielles de notre application, examinons la consommation de la mémoire. Nous voyons ici que nous transférons 1,38 Mo d'instructions de dessin dans les 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 nous contentons de redimensionner ou d'agrandir la fenêtre du navigateur, nous voyons la mémoire se développer également.

Coreanimation 2

Cela vous donne une idée de la façon dont la mémoire est utilisée sur votre appareil mobile uniquement si vous redimensionnez le navigateur aux bonnes dimensions. Si vous effectuez le débogage ou le test pour des environnements iPhone, redimensionnez votre appareil à 480 x 320 pixels. Nous comprenons maintenant exactement le fonctionnement de l'accélération matérielle et les opérations de débogage. C'est une chose de lire à ce sujet, mais le fait de voir que les tampons de mémoire du GPU fonctionnent visuellement permet de mettre les choses en perspective.

Dans les coulisses: récupération et mise en cache

Il est maintenant temps de faire passer la mise en cache des pages et des ressources au niveau supérieur. Comme pour l'approche de JQuery Mobile et d'environnements similaires, nous allons précharger nos pages et les mettre en cache avec des appels AJAX simultanés.

Intéressons-nous maintenant à quelques-uns des principaux problèmes liés au Web pour mobile, ainsi qu'aux raisons pour lesquelles nous devons le faire:

  • Récupération: la prélecture de nos pages permet aux utilisateurs de mettre l'application hors connexion et évite d'attendre entre deux actions de navigation. Bien sûr, nous ne voulons pas étouffer la bande passante de l'appareil lorsque celui-ci se connecte, nous devons donc utiliser cette fonctionnalité avec parcimonie.
  • Mise en cache: nous souhaitons maintenant une approche simultanée ou asynchrone pour récupérer et mettre en cache ces pages. Nous devons également utiliser localStorage (car il est compatible avec tous les appareils), qui n'est malheureusement pas asynchrone.
  • AJAX et analyse de la réponse: l'utilisation de innerHTML() pour insérer la réponse AJAX dans le DOM est dangereuse (et manque de fiabilité ?). 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 de nouvelles fonctionnalités de HTML5 pour analyser xhr.responseText.

En nous appuyant sur le code de la démonstration "Faire glisser, inverser" et faire pivoter, nous commençons par ajouter des pages secondaires et des liens vers celles-ci. Nous analyserons ensuite les liens et créerons des transitions à la volée.

Accueil iPhone

Regardez la démonstration de la récupération et du cache.

Comme vous pouvez le voir, 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œud/classe que son parent. Nous pourrions aller 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>

Examinons maintenant le script JavaScript. Par souci de simplicité, nous n'incluons aucune assistance ni aucune optimisation dans le code. Tout ce que nous faisons ici consiste à parcourir un tableau spécifié de nœuds DOM en boucle pour extraire les liens à récupérer et à mettre en cache. Remarque : Dans cette démonstration, la méthode fetchAndCache() est appelée lors du chargement de la page. Dans la section suivante, nous y reviendrons lorsque nous détecterons la connexion réseau et déterminerons quand l'appeler.

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é via l'objet "AJAX". Pour obtenir une explication plus détaillée de l'utilisation de localStorage dans un appel AJAX, reportez-vous à la section Travailler en dehors de la grille avec HTML5 hors connexion. Dans cet exemple, vous voyez l'utilisation de base de la mise en cache sur chaque requête et la fourniture des objets mis en cache lorsque le serveur renvoie une réponse autre qu'une réponse positive (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, localStorage utilisant l'encodage UTF-16 pour les caractères, chaque octet est stocké sous la forme de 2 octets.Notre limite de stockage est donc de 5 Mo à 2,6 Mo au total. La raison complète de la récupération et de la mise en cache de ces pages/balisage en dehors du champ d'application du cache de l'application est dévoilée dans la section suivante.

Grâce aux progrès récents dans l'élément iFrame avec HTML5, nous disposons désormais d'un moyen simple et efficace d'analyser la responseText renvoyée 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 responseText dans un iFrame masqué temporaire. Nous utilisons l'attribut HTML5 "sandbox" qui désactive les scripts et offre de nombreuses fonctionnalités de sécurité...

Conformément à la spécification, l'attribut sandbox, s'il est spécifié, 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 un espace, non sensibles à la casse au format ASCII. Les valeurs autorisées sont allow-forms, allow-same-origin, allow-scripts et allow-top-navigation. Lorsque cet 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 se passe bien.

Pourquoi utiliser 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 (mal ou bien) dans une zone non cochée. Au cours de la rédaction de cet article, je n'ai trouvé personne qui utilise autre chose que innerHTML. Je sais que JQuery l'utilise essentiellement, avec un ajout de remplacement en cas d'exception uniquement. JQuery Mobile l'utilise également. Cependant, je n'ai pas effectué de tests approfondis en ce qui concerne innerHTML interne 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 entendu des déclarations de part et d'autre à ce sujet.

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

Maintenant que nous pouvons mettre en mémoire tampon (ou cache prédictif) notre application Web, nous devons fournir les fonctionnalités de détection de connexion appropriées pour rendre 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. Découvrez l'API Network Information. Chaque fois que je la présente cette fonctionnalité dans une présentation, un membre du public lève la main et demande "À quoi peut-il servir ?". Voici donc un moyen de configurer une application Web mobile extrêmement intelligente.

Tout d'abord, un scénario ennuyeux du bon sens... Lors d'une interaction avec le Web depuis un appareil mobile à bord d'un train à grande vitesse, le réseau peut très bien disparaître à différents moments et différentes zones géographiques peuvent prendre en charge différentes vitesses de transmission (par exemple, Le HSPA ou la 3G peuvent être disponibles dans certaines zones urbaines, mais les zones reculées peuvent accepter des technologies 2G beaucoup plus lentes. Le code suivant répond à la plupart des scénarios de connexion.

Le code suivant fournit:

  • Accès hors connexion via applicationCache.
  • Détecte s'il est ajouté aux favoris et hors connexion.
  • Détecte quand un utilisateur passe de l'état hors connexion à l'activité en ligne et inversement.
  • Détecte les connexions lentes et récupère du contenu en fonction du type de réseau.

Là encore, toutes ces fonctionnalités nécessitent très peu de code. Nous commençons par détecter 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 écouteurs d'événements ci-dessus, nous devons indiquer au code s'il est appelé à partir d'un événement, d'une requête ou d'une actualisation de page. En effet, l'événement onload du corps ne se déclenche pas lorsque vous passez du mode en ligne au mode hors connexion.

Ensuite, nous avons une vérification simple pour un événement ononline ou onload. Ce code réinitialise les liens désactivés lors du passage de l'état hors connexion à l'état en ligne. Toutefois, si cette application était plus sophistiquée, vous pouvez insérer une logique qui reprendrait la récupération du contenu ou qui 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(). Ici, vous manipuleriez votre application pour le mode hors connexion et essayeriez de récupérer toutes les transactions qui se déroulaient en arrière-plan. Le code ci-dessous examine tous nos liens externes et les désactive. Les utilisateurs sont ainsi piégés dans notre application hors connexion FOREVER, 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);

    }
  }
}

Passons à la bonne chose. 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é, pour chaque connexion, les temps de téléchargement et les temps de latence habituels pour les fournisseurs nord-américains.

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);
  }
}

Il existe de nombreux ajustements que nous pourrions apporter à notre processus fetchAndCache, mais tout ce que j'ai fait ici consistait à lui demander d'extraire les ressources de manière asynchrone (true) ou synchrone (false) pour une connexion donnée.

Chronologie des requêtes Edge (synchrone)

Synchronisation en périphérie

Chronologie d'une requête Wi-Fi (asynchrone)

Wi-Fi asynchrone

Cela permet au moins d'ajuster l'expérience utilisateur en fonction des connexions lentes ou rapides. Il ne s'agit en aucun cas d'une solution complète. Une autre tâche consiste à lancer une fenêtre modale de chargement lorsque l'utilisateur clique sur un lien (en cas de connexion lente), alors que l'application est encore en train d'explorer la page de ce lien en arrière-plan. L'essentiel ici est de réduire les latences tout en exploitant pleinement le potentiel de la connexion de l'utilisateur avec le HTML5 le plus récent et le plus performant. Regardez la démonstration de la détection de réseau ici.

Conclusion

L'évolution des applications HTML5 pour mobile ne fait que commencer. Vous découvrez maintenant les fondements simples et les plus fondamentals d'un "framework" pour mobile construit uniquement autour de HTML5 et de ses technologies compatibles. Je pense qu'il est important pour les développeurs de travailler avec ces fonctionnalités et de les gérer comme avant tout, et non d'être masquées par un wrapper.