Adapter votre site HTML5 aux mobiles

Introduction

Le développement pour le Web mobile est un sujet très en vogue actuellement. Cette année, pour la première fois, les ventes de smartphones ont dépassé celles des PC. De plus en plus d'utilisateurs se servent d'un appareil mobile pour naviguer sur le Web. Il est donc devenu essentiel pour les développeurs d'optimiser leurs sites pour les navigateurs mobiles.

Le champ de bataille "mobile" reste un territoire inexploré pour un grand nombre de développeurs. De nombreux sites existants négligent complètement les utilisateurs mobiles. Au lieu de cela, le site a été conçu principalement pour la navigation sur ordinateur et se dégrade mal dans les navigateurs mobiles. Ce site (html5rocks.com) ne fait pas exception. Lors du lancement, nous n'avons pas consacré beaucoup d'efforts à la version mobile du site.

Créer une version de html5rocks.com adaptée aux mobiles

À titre d'exercice, j'ai pensé qu'il serait intéressant de prendre html5rocks (un site HTML5 existant) et de l'enrichir d'une version adaptée aux mobiles. Je me suis principalement concentré sur la quantité minimale de travail requise pour cibler les smartphones. L'objectif de mon exercice n'était pas de créer un site mobile entièrement nouveau et de gérer deux codebases. Cela aurait pris une éternité et aurait été une énorme perte de temps. Nous avions déjà défini la structure (balisage) du site. Nous avions une apparence (CSS). La fonctionnalité de base (JS) était là. Le fait est que de nombreux sites sont dans la même situation.

Cet article explique comment nous avons créé une version mobile de html5rocks optimisée pour les appareils Android et iOS. Il vous suffit de charger html5rocks.com sur un appareil compatible avec l'un de ces OS pour voir la différence. Il n'y a pas de redirection vers m.html5rocks.com ni d'autres bêtises de ce genre. Vous obtenez html5rocks tel quel… avec l'avantage supplémentaire d'un site qui s'affiche correctement et fonctionne bien sur un appareil mobile.

html5rocks.com pour ordinateur Mobile html5rocks.com
html5rocks.com sur ordinateur (à gauche) et sur mobile (à droite)

Requêtes média CSS

HTML4 et CSS2 sont compatibles avec les feuilles de style dépendantes du média depuis un certain temps. Exemple :

<link rel="stylesheet" media="print" href="printer.css">

cible les appareils d'impression et fournit un style spécifique pour le contenu de la page lorsqu'elle est imprimée. CSS3 va encore plus loin dans l'idée des types de médias et améliore leurs fonctionnalités avec les requêtes média. Les requêtes média étendent l'utilité des types de médias en permettant un étiquetage plus précis des feuilles de style. Cela permet de personnaliser la présentation du contenu pour une gamme spécifique de périphériques de sortie sans avoir à modifier le contenu lui-même. C'est parfait pour une mise en page existante qui doit être modifiée.

Vous pouvez utiliser des requêtes média dans l'attribut media de vos feuilles de style externes pour cibler la largeur de l'écran, la largeur de l'appareil, l'orientation, etc. Pour obtenir la liste complète, consultez la spécification des requêtes média du W3C.

Cibler des tailles d'écran

Dans l'exemple suivant, phone.css s'applique aux appareils que le navigateur considère comme "portables" ou aux appareils dont l'écran est d'une largeur inférieure ou égale à 320 px.

 <link rel='stylesheet'
  media='handheld, only screen and (max-device-width: 320px)' href='phone.css'>

Si vous ajoutez le mot clé "only" devant une requête média, les navigateurs non compatibles avec CSS3 ignoreront la règle.

Le code suivant cible les tailles d'écran comprises entre 641 px et 800 px :

 <link rel='stylesheet'
  media='only screen and (min-width: 641px) and (max-width: 800px)' href='ipad.css'>

Les requêtes média peuvent également apparaître dans des balises <style> intégrées. Le code suivant cible les types de supports all en orientation portrait :

 <style>
  @media only all and (orientation: portrait) { ... }
 </style>

media="handheld"

Nous devons nous arrêter un instant pour parler de media="handheld". En réalité, Android et iOS ignorent media="handheld". L'argument est que les utilisateurs ne pourront pas profiter du contenu haut de gamme fourni par les feuilles de style ciblant media="screen" et que les développeurs sont moins susceptibles de maintenir une version media="handheld" de qualité inférieure. Ainsi, dans le cadre de leur devise "full web", la plupart des navigateurs de smartphones modernes ignorent tout simplement les feuilles de style pour appareils portables.

Il serait idéal d'utiliser cette fonctionnalité pour cibler les appareils mobiles, mais les différents navigateurs l'ont implémentée de différentes manières :

  • Certains ne lisent que la feuille de style pour les appareils portables.
  • Certains ne lisent que la feuille de style pour les appareils portables, s'il y en a une, mais utilisent par défaut la feuille de style pour l'écran dans le cas contraire.
  • Certains lisent à la fois la feuille de style pour les appareils portables et celle pour les écrans.
  • Certains ne lisent que la feuille de style de l'écran.

Opera Mini n'ignore pas media="handheld". Pour que Windows Mobile reconnaisse media="handheld", vous devez mettre en majuscule la valeur de l'attribut multimédia de la feuille de style de l'écran :

 <!-- media="handheld" trick for Windows Mobile -->
 <link rel="stylesheet" href="screen.css" media="Screen">
 <link rel="stylesheet" href="mobile.css" media="handheld">

Comment html5rocks utilise les requêtes média

Les requêtes média sont largement utilisées dans mobile.html5rocks. Ils nous ont permis de modifier la mise en page sans avoir à apporter de modifications importantes à notre balisage de modèle Django… Un vrai soulagement ! De plus, ils sont bien pris en charge par les différents navigateurs.

Dans la section <head> de chaque page, vous verrez les feuilles de style suivantes :

 <link rel='stylesheet'
  media='all' href='/static/css/base.min.css' />
 <link rel='stylesheet'
  media='only screen and (max-width: 800px)' href='/static/css/mobile.min.css' />

base.css a toujours défini l'apparence principale de html5rocks.com, mais nous appliquons désormais de nouveaux styles (mobile.css) pour les largeurs d'écran inférieures à 800 px. Sa requête média couvre les smartphones (~320 px) et l'iPad (~768 px). Résultat : nous remplaçons progressivement les styles dans base.css (uniquement si nécessaire) pour améliorer l'affichage sur mobile.

Voici quelques-unes des modifications de style appliquées par mobile.css :

  • Réduit les espaces blancs/le remplissage supplémentaires sur le site. Sur les petits écrans, l'espace est limité.
  • Supprime :hover états. Ils ne s'afficheront jamais sur les appareils tactiles.
  • Ajuste la mise en page pour qu'elle soit sur une seule colonne. Nous reviendrons sur ce point.
  • Supprime l'élément box-shadow autour de la div du conteneur principal du site. Les ombres de grande taille réduisent les performances de la page.
  • Utilisation du modèle de boîte flexible CSS box-ordinal-group pour modifier l'ordre de chaque section sur la page d'accueil. Vous remarquerez que la section "LEARN BY MAJOR HTML5 FEATURE GROUPS " (Apprendre par groupes de fonctionnalités HTML5 majeurs) apparaît avant la section"TUTORIALS " (Tutoriels) sur la page d'accueil, mais après celle-ci sur la version mobile. Cet ordre était plus logique pour les mobiles et ne nécessitait pas de modifier le balisage. Vive les flexbox CSS !
  • Supprime opacity modifications. La modification des valeurs alpha a un impact sur les performances sur mobile.

Balises Meta pour mobile

Mobile WebKit est compatible avec quelques fonctionnalités qui offrent aux utilisateurs une meilleure expérience de navigation sur certains appareils.

Paramètres de la fenêtre d'affichage

Le premier paramètre meta (et celui que vous utiliserez le plus souvent) est la propriété viewport. La définition d'une fenêtre d'affichage indique au navigateur comment le contenu doit s'adapter à l'écran de l'appareil et l'informe que le site est optimisé pour les mobiles. Exemple :

 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">

indique au navigateur de définir la fenêtre d'affichage sur la largeur de l'appareil avec une échelle initiale de 1. Cet exemple permet également d'effectuer un zoom, ce qui peut être souhaitable pour un site Web, mais pas pour une application Web. Nous pouvons empêcher le zoom avec user-scalable=no ou limiter le scaling à un certain niveau :

 <meta name=viewport
  content="width=device-width, initial-scale=1.0, minimum-scale=0.5 maximum-scale=1.0">

Android étend la balise Meta viewport en permettant aux développeurs de spécifier la résolution d'écran pour laquelle le site a été développé :

 <meta name="viewport" content="target-densitydpi=device-dpi">

Les valeurs possibles pour target-densitydpi sont device-dpi, high-dpi, medium-dpi et low-dpi.

Si vous souhaitez modifier votre page Web pour différentes densités d'écran, utilisez la requête média CSS -webkit-device-pixel-ratio et/ou la propriété window.devicePixelRatio en JavaScript, puis définissez la propriété meta target-densitydpi sur device-dpi. Cela empêche Android d'effectuer une mise à l'échelle de votre page Web et vous permet d'apporter les ajustements nécessaires pour chaque densité, via CSS et JavaScript.

Pour en savoir plus sur le ciblage des résolutions d'appareils, consultez la documentation sur WebView d'Android.

Navigation en plein écran

Il existe deux autres métavaleurs spécifiques à iOS. apple-mobile-web-app-capable et apple-mobile-web-app-status-bar-style affichent le contenu de la page en mode plein écran, comme dans une application, et rendent la barre d'état translucide :

 <meta name="apple-mobile-web-app-capable" content="yes">
 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

Pour en savoir plus sur toutes les options de métadonnées disponibles, consultez la documentation de référence de Safari.

Icônes de l'écran d'accueil

Les appareils iOS et Android acceptent également les paramètres rel="apple-touch-icon" (iOS) et rel="apple-touch-icon-precomposed" (Android) pour les liens. Elles créent une icône flashy semblable à celle d'une application sur l'écran d'accueil de l'utilisateur lorsqu'il ajoute votre site à ses favoris :

 <link rel="apple-touch-icon"
      href="/static/images/identity/HTML5_Badge_64.png" />
 <link rel="apple-touch-icon-precomposed"
      href="/static/images/identity/HTML5_Badge_64.png" />

Comment html5rocks utilise les balises Meta pour mobile

En résumé, voici un extrait de la section <head> de html5rocks :

 <head>
  ...
   <meta name="viewport"
        content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />

   <link rel="apple-touch-icon"
        href="/static/images/identity/HTML5_Badge_64.png" />
   <link rel="apple-touch-icon-precomposed"
        href="/static/images/identity/HTML5_Badge_64.png" />
  ...
 </head>

Mise en page verticale

Sur les petits écrans, il est beaucoup plus pratique de faire défiler l'écran verticalement qu'horizontalement. Pour les mobiles, il est préférable de conserver le contenu dans une mise en page verticale à une seule colonne. Pour html5rocks, nous avons utilisé des requêtes média CSS3 pour créer une telle mise en page. Là encore, sans modifier le balisage.

Index des tutoriels. Tutoriel. Page des fonctionnalités HTML5. Page de profils d'auteur.
Mise en page verticale sur une seule colonne sur l'ensemble du site.

Optimisations pour mobile

La plupart des optimisations que nous avons effectuées auraient dû être faites dès le départ. Par exemple, réduire le nombre de requêtes réseau, compresser les fichiers JS/CSS, utiliser la compression gzip (disponible sans frais sur App Engine) et limiter les manipulations du DOM. Ces techniques sont des bonnes pratiques courantes, mais elles sont parfois négligées lorsque l'on se précipite pour publier un site.

Masquer automatiquement la barre d'adresse

Les navigateurs mobiles ne disposent pas de l'espace d'écran de leur équivalent pour ordinateur. Pour ne rien arranger, sur certaines plates-formes, une grande barre d'adresse s'affiche en haut de l'écran, même une fois le chargement de la page terminé.

Une solution simple consiste à faire défiler la page à l'aide de JavaScript. Même un défilement d'un pixel suffit à masquer la barre d'adresse. Pour forcer le masquage de la barre d'adresse sur html5rocks, j'ai associé un gestionnaire d'événements onload à l'objet window et j'ai fait défiler la page verticalement d'un pixel :

Barre d'adresse.
La barre d'adresse prend trop de place sur l'écran.
  // Hides mobile browser's address bar when page is done loading.
  window.addEventListener('load', function(e) {
    setTimeout(function() { window.scrollTo(0, 1); }, 1);
  }, false);

Nous avons également encapsulé cet écouteur dans notre variable de modèle is_mobile, car il n'est pas nécessaire sur ordinateur.

Réduire les requêtes réseau, économiser de la bande passante

Il est bien connu que la réduction du nombre de requêtes HTTP peut améliorer considérablement les performances. Les appareils mobiles limitent encore davantage le nombre de connexions simultanées que le navigateur peut établir. Les sites mobiles bénéficieront donc encore plus de la réduction de ces requêtes superflues. De plus, il est essentiel de réduire au maximum la taille des fichiers, car la bande passante est souvent limitée sur les téléphones. Vous risquez de faire perdre de l'argent aux utilisateurs.

Voici quelques-unes des approches que nous avons adoptées pour minimiser les requêtes réseau et réduire la bande passante sur html5rocks :

  • Supprimez les iFrames, car ils sont lents. Une grande partie de notre latence provenait de widgets de partage tiers (Buzz, Google Friend Connect, Twitter, Facebook) sur les pages de tutoriels. Ces API ont été incluses via des balises <script> et créent des iFrames qui ralentissent la page. Les widgets ont été supprimés pour les mobiles.

  • display:none : dans certains cas, nous masquions le balisage s'il ne correspondait pas au profil mobile. Les quatre zones arrondies en haut de la page d'accueil en sont un bon exemple :

Boutons encadrés sur la page d'accueil.
Boutons de zone sur la page d'accueil.

Ils ne sont pas présents sur le site mobile. Il est important de noter que le navigateur envoie toujours une requête pour chaque icône, même si leur conteneur est masqué avec display:none. Il ne suffisait donc pas de simplement masquer ces boutons. Non seulement cela gaspillerait de la bande passante, mais l'utilisateur ne verrait même pas les fruits de cette bande passante gaspillée. La solution consistait à créer une variable booléenne "is_mobile" dans notre modèle Django pour omettre de manière conditionnelle des sections de code HTML. Lorsque l'utilisateur consulte le site sur un appareil connecté, les boutons sont laissés de côté.

  • Cache d'application : non seulement cela nous permet de prendre en charge le mode hors connexion, mais cela accélère également le démarrage.

  • Compression CSS/JS : nous utilisons le compresseur YUI au lieu du compilateur Closure, principalement parce qu'il gère à la fois le CSS et le JS. Nous avons rencontré un problème : les requêtes média intégrées (requêtes média qui apparaissent dans une feuille de style) ont été rejetées par YUI Compressor 2.4.2 (voir ce problème). L'utilisation de YUI Compressor 2.4.4 ou version ultérieure a résolu le problème.

  • Utilisez des sprites d'image CSS dans la mesure du possible.

  • Utilisation de pngcrush pour la compression d'images.

  • Utilisation d'URI de données pour les petites images. L'encodage en base64 augmente la taille de l'image d'environ 30 %, mais permet d'économiser la requête réseau.

  • Chargement automatique de la recherche personnalisée Google à l'aide d'un seul tag de script plutôt que de la charger de manière dynamique avec google.load(). Cette dernière effectue une requête supplémentaire.

<script src="//www.google.com/jsapi?autoload={"modules":[{"name":"search","version":"1"}]}"> </script>
  • Notre outil d'impression élégante de code et Modernizr étaient inclus sur chaque page, même s'ils n'étaient jamais utilisés. Modernizr est un outil formidable, mais il exécute un certain nombre de tests à chaque chargement. Certains de ces tests apportent des modifications coûteuses au DOM et ralentissent le chargement de page. Désormais, nous n'incluons ces bibliothèques que sur les pages où elles sont réellement nécessaires. -2 requêtes :)

Autres ajustements des performances :

  • Déplacement de tout le code JS vers le bas de la page (dans la mesure du possible).
  • Suppression des balises <style> intégrées.
  • Recherches DOM mises en cache et manipulations DOM minimisées : chaque fois que vous touchez le DOM, le navigateur effectue un reflow. Les redistributions sont encore plus coûteuses sur un appareil mobile.
  • Le code côté client inutile a été déchargé sur le serveur. Plus précisément, la vérification permettant de définir le style de navigation de la page actuelle : js var lis = document.querySelectorAll('header nav li'); var i = lis.length; while (i--) { var a = lis[i].querySelector('a'); var section = a.getAttribute("data-section"); if (new RegExp(section).test(document.location.href)) { a.className = 'current'; } }
  • Les éléments de largeur fixe ont été remplacés par des éléments fluides width:100% ou width:auto.

Cache de l'application

La version mobile de html5rocks utilise le cache d'application pour accélérer le chargement initial et permettre aux utilisateurs de lire du contenu hors connexion.

Lorsque vous implémentez AppCache sur votre site, il est très important de ne pas mettre en cache votre fichier manifeste (que ce soit explicitement dans le fichier manifeste lui-même ou implicitement avec des en-têtes de contrôle de cache lourds). Si votre fichier manifeste est mis en cache par le navigateur, il est très difficile de le déboguer. iOS et Android mettent particulièrement bien en cache ce fichier, mais ne fournissent pas de moyen pratique de vider le cache, contrairement aux navigateurs pour ordinateur.

Pour empêcher cette mise en cache pour notre site, nous configurons d'abord App Engine pour qu'il ne mette jamais en cache les fichiers manifestes :

- url: /(.*\.(appcache|manifest))
  static_files: \1
  mime_type: text/cache-manifest
  upload: (.*\.(appcache|manifest))
  expiration: "0s"

Ensuite, nous avons utilisé l'API JS pour informer l'utilisateur lorsqu'un nouveau fichier manifeste a été téléchargé. Ils sont invités à actualiser la page :

window.applicationCache.addEventListener('updateready', function(e) {
  if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
    window.applicationCache.swapCache();
    if (confirm('A new version of this site is available. Load it?')) {
      window.location.reload();
    }
  }
}, false);

Pour économiser du trafic réseau, simplifiez votre fichier manifeste. Autrement dit, n'indiquez pas toutes les pages de votre site. Répertoriez simplement les images, les fichiers CSS et les fichiers JavaScript importants. La dernière chose que vous voulez faire est de forcer le navigateur mobile à télécharger un grand nombre d'assets à chaque mise à jour d'appcache. N'oubliez pas que le navigateur met implicitement en cache une page HTML lorsque l'utilisateur la consulte (et qu'elle inclut un attribut <html manifest="...">).