Le Frontal de la Terre du Milieu

Tutoriel du développement multi-appareil

Daniel Isaksson
Daniel Isaksson

Dans notre premier article consacré au développement de l'expérience Chrome Un périple à travers la Terre du Milieu, nous nous sommes concentrés sur le développement WebGL pour les appareils mobiles. Dans cet article, nous abordons les difficultés, les problèmes et les solutions que nous avons rencontrés lors de la création du reste de l'interface HTML5.

Trois versions du même site

Voyons d'abord comment adapter cette expérience afin qu'elle fonctionne à la fois sur les ordinateurs de bureau et sur les appareils mobiles, du point de vue de la taille d'écran et des fonctionnalités de l'appareil.

L'ensemble du projet est basé sur un style très "cinématique", dans lequel nous voulions conserver l'expérience dans un cadre fixe en mode paysage pour préserver la magie du film. Étant donné qu'une grande partie du projet est constituée de mini-jeux interactifs, il ne serait pas logique de les laisser dépasser du cadre.

Nous pouvons prendre la page d’accueil comme exemple de la façon dont nous adaptons la conception à différentes tailles.

Les aigles viennent de nous laisser sur la page de destination.
Les aigles viennent de nous laisser sur la page de destination.

Le site propose trois modes différents: ordinateur, tablette et mobile. Non seulement pour gérer la mise en page, mais aussi parce que nous devons gérer les éléments chargés à l'exécution et ajouter diverses optimisations de performances. Avec des appareils dont la résolution est supérieure à celle des ordinateurs de bureau et des ordinateurs portables, mais dont les performances sont inférieures à celles des téléphones, il n'est pas facile de définir l'ensemble de règles final.

Nous utilisons les données des user-agents pour détecter les appareils mobiles, ainsi qu'un test de la taille de la fenêtre d'affichage pour cibler les tablettes parmi celles-ci (de 645 pixels et plus). Chaque mode peut afficher toutes les résolutions, car la mise en page est basée sur des requêtes média ou sur un positionnement relatif ou en pourcentage avec JavaScript.

Dans ce cas, les conceptions ne sont pas basées sur des grilles ou des règles et sont assez uniques entre les différentes sections. Cela dépend en fait de l'élément et du scénario spécifiques des points d'arrêt ou des styles à utiliser. Il s'est produit plusieurs fois que nous avions configuré la mise en page parfaite avec des combinaisons suss et des requêtes média. Nous devions ensuite ajouter un effet basé sur la position de la souris ou les objets dynamiques, et nous avons fini par tout réécrire en JavaScript.

Nous ajoutons également une classe avec le mode actuel dans la balise "head" afin de pouvoir utiliser cette information dans nos styles, comme dans cet exemple (en SCSS):

.loc-hobbit-logo {

  // Default values here.

  .desktop & {
     // Applies only in desktop mode.
  }

 .tablet &, .mobile & {
   
   // Different asset for mobile and tablets perhaps.

   @media screen and (max-height: 760px), (max-width: 760px) {
     // Breakpoint-specific styles.
   }

   @media screen and (max-height: 570px), (max-width: 400px) {
     // Breakpoint-specific styles.
   }
 }
}

Nous acceptons toutes les tailles jusqu'à environ 360 x 320, ce qui représente un défi assez difficile pour créer une expérience Web immersive. Sur ordinateur, une taille minimale est imposée avant d'afficher les barres de défilement, car nous souhaitons que le site s'affiche dans une fenêtre d'affichage plus grande, si possible. Sur les appareils mobiles, nous avons décidé d'autoriser les modes paysage et portrait jusqu'aux expériences interactives, où nous vous demandons d'activer le mode Paysage. L'argument contre cela était qu'il n'est pas aussi immersif en mode portrait qu'en mode paysage. Toutefois, le site s'est assez bien adapté, c'est pourquoi nous l'avons gardé.

Il est important de noter que la mise en page ne doit pas être confondue avec la détection de fonctionnalités telles que le type d'entrée, l'orientation de l'appareil, les capteurs, etc. Ces fonctionnalités peuvent exister dans tous ces modes et doivent s'étendre à tous. La prise en charge simultanée de la souris et du toucher en est un exemple. Compensation de la rétine pour la qualité, mais la plupart des performances en sont une autre. Parfois, une qualité moindre est meilleure. Par exemple, le canevas a la moitié de la résolution dans les expériences WebGL sur les écrans Retina, auquel cas il devrait afficher quatre fois le nombre de pixels.

Nous avons fréquemment utilisé l'outil émulateur dans les outils de développement pendant le développement, en particulier dans Chrome Canary, qui propose de nouvelles fonctionnalités améliorées et de nombreux préréglages. C'est un bon moyen de valider rapidement une conception. Nous devions toujours effectuer régulièrement des tests sur de vrais appareils. Cela s'explique notamment par le fait que le site s'adapte au plein écran. Dans la plupart des cas, les pages à défilement vertical masquent l'interface utilisateur du navigateur (Safari sur iOS 7 rencontre actuellement des problèmes), mais nous devions adapter tout cela indépendamment de cela. Nous avons également utilisé un préréglage dans l'émulateur et modifié le paramètre de taille de l'écran pour simuler la perte d'espace disponible. Les tests sur des appareils réels sont également importants pour surveiller la consommation de mémoire et les performances

Gérer l'état

Après la page de destination, nous arrivons sur la carte de la Terre du Milieu. Avez-vous remarqué que l'URL changeait ? Il s'agit d'une application monopage qui gère le routage à l'aide de l'API History.

Chaque section du site est un objet qui hérite d'un code récurrent de fonctionnalités telles que des éléments DOM, des transitions, le chargement des éléments, la suppression, etc. Lorsque vous explorez différentes parties du site, les sections sont lancées, les éléments sont ajoutés et supprimés du DOM, et les éléments de la section actuelle sont chargés.

Étant donné que l'utilisateur peut appuyer sur le bouton Retour du navigateur ou naviguer via le menu à tout moment, tout ce qui est créé doit être supprimé à un moment donné. Les délais d'inactivité et les animations doivent être arrêtés et supprimés, car ils peuvent entraîner des comportements indésirables, des erreurs et des fuites de mémoire. Ce n'est pas toujours une tâche facile, surtout lorsque les échéances approchent et que vous devez tout arriver le plus vite possible.

Présenter les lieux

Pour mettre en valeur les magnifiques paramètres et les personnages de la Terre du Milieu, nous avons créé un système modulaire de composants d'image et de texte que vous pouvez faire glisser ou balayer horizontalement. Nous n'avons pas activé de barre de défilement ici, car nous voulons avoir des vitesses différentes sur différentes plages, par exemple dans les séquences d'images où vous arrêtez le mouvement latéralement jusqu'à ce que l'extrait soit terminé.

Thranduil's Hall
Chronologie de Thranduil's Hall

La chronologie

Au début du développement, nous ne connaissions pas le contenu des modules de chaque site. Ce que nous savions, c'est que nous voulions un moyen modélisé de présenter différents types de médias et d'informations dans une chronologie horizontale qui nous donnerait la liberté d'avoir six présentations de lieux différentes sans avoir à tout reconstruire six fois. Pour ce faire, nous avons créé un contrôleur de chronologie qui gère le panoramique de ses modules en fonction des paramètres et des comportements de ces modules.

Modules et composants de comportement

Nous avons ajouté la compatibilité avec les différents modules suivants : séquence d'images, image fixe, scène parallaxe, scène avec décalage de mise au point et texte.

Le module de scène parallaxe présente un arrière-plan opaque avec un nombre personnalisé de couches qui écoute la progression de la fenêtre d'affichage pour obtenir les positions exactes.

La scène de décalage de mise au point est une variante du bucket parallaxe, avec en plus l'utilisation de deux images pour chaque calque, qui apparaissent et disparaissent en fondu pour simuler un changement de sélection. Nous avons essayé d'utiliser le filtre de floutage, mais son coût reste trop élevé. Nous attendons donc des nuanceurs CSS pour obtenir ce résultat.

Il est possible de faire glisser le contenu du module de texte avec le plug-in TweenMax Draggable. Vous pouvez également utiliser la molette ou le balayage avec deux doigts pour faire défiler l'écran verticalement. Notez le plug-in throw-props-plugin qui ajoute les effets physiques de balayage lorsque vous balayez l'écran et relâchez les touches.

Les modules peuvent également avoir différents comportements qui sont ajoutés sous la forme d'un ensemble de composants. Elles disposent toutes de leurs propres sélecteurs de cibles et paramètres. Traduisez pour déplacer un élément, adapter la mise à l'échelle au zoom, zones cliquables pour la superposition d'informations, déboguer des métriques à des fins visuelles de test, superposition de titre de début, calque de reflets, etc. Ceux-ci seront ajoutés au DOM ou en contrôlant leur élément cible dans le module.

Une fois cela en place, nous pouvons créer les différents emplacements avec un simple fichier de configuration qui définit les éléments à charger et configurer les différents types de modules et de composants.

Séquences d'images

La séquence d'images est le module le plus complexe en termes de performances et de taille de téléchargement. Il y a beaucoup de choses à lire sur ce sujet. Sur les mobiles et les tablettes, nous le remplaçons par une image fixe. Le décodage et le stockage en mémoire sont trop importants pour une qualité satisfaisante sur mobile. Nous avons essayé plusieurs solutions alternatives en utilisant d'abord une image de fond et une feuille de sprites, mais cela entraînait des problèmes de mémoire et des retards lorsque le GPU devait passer d'une feuille de sprites à une autre. Ensuite, nous avons essayé de permuter les éléments img, mais c'était aussi trop lent. Il était plus efficace de dessiner un cadre à partir d'une feuille de sprites sur un canevas. Nous avons donc commencé à l'optimiser. Pour gagner du temps de calcul à chaque image, les données d'image à écrire dans le canevas sont prétraitées sur un canevas temporaire et enregistrées avec putImageData() dans un tableau, décodées et prêtes à l'emploi. La feuille de sprites d'origine peut alors être récupérée, et nous ne stockons que la quantité minimale de données nécessaire en mémoire. Il est peut-être moins efficace de stocker des images non décodées, mais nous obtenons de meilleures performances en nettoyant la séquence de cette manière. Les cadres sont assez petits (à peine 640 x 400), mais ils ne sont visibles que lorsque vous utilisez la barre de lecture. Lorsque vous vous arrêtez, une image haute résolution se charge et apparaît rapidement en fondu.

var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;

var ctx = canvas.getContext('2d');
ctx.drawImage(sheet, 0, 0);

var tilesX = imageWidth / tileWidth;
var tilesY = imageHeight / tileHeight;

var canvasPaste = canvas.cloneNode(false);
canvasPaste.width = tileWidth;
canvasPaste.height = tileHeight;

var i, j, canvasPasteTemp, imgData, 
var currentIndex = 0;
var startIndex = index * 16;
for (i = 0; i < tilesY; i++) {
  for (j = 0; j < tilesX; j++) {
    // Store the image data of each tile in the array.
    canvasPasteTemp = canvasPaste.cloneNode(false);
    imgData = ctx.getImageData(j * tileWidth, i * tileHeight, tileWidth, tileHeight);
    canvasPasteTemp.getContext('2d').putImageData(imgData, 0, 0);

    list[ startIndex + currentIndex ] = imgData;

    currentIndex++;
  }
}

Les feuilles de sprites sont générées avec Imagemagick. Voici un exemple simple sur GitHub qui montre comment créer une feuille de sprites de toutes les images d'un dossier.

Animer les modules

Pour placer les modules sur la timeline, une représentation cachée de la timeline, affichée hors écran, effectue le suivi de la "tête de lecture" et de la largeur de la timeline. Pour cela, il suffit d'utiliser du code, mais l'utilisation d'une représentation visuelle était efficace lors du développement et du débogage. En situation réelle, l'application est simplement mise à jour lors du redimensionnement pour définir les dimensions. Certains modules remplissent la fenêtre d'affichage, tandis que d'autres ont leur propre format. Il était donc un peu difficile de tout mettre à l'échelle et de tout positionner dans toutes les résolutions, de sorte que tout soit visible et pas trop recadré. Chaque module est accompagné de deux indicateurs de progression, l'un correspondant à la position visible à l'écran et l'autre à la durée du module lui-même. Lors d'un mouvement parallaxe, il est souvent difficile de calculer les positions de départ et de fin des objets à synchroniser avec la position attendue lorsqu'ils sont dans le champ de vision. Il est utile de savoir exactement quand un module entre dans la vue, lit sa timeline interne et quand il s'anime de nouveau hors de la vue.

Chaque module est surmonté d'une couche noire subtile qui ajuste son opacité de sorte qu'il soit totalement transparent lorsqu'il se trouve au centre. Cela vous permet de vous concentrer sur un module à la fois, ce qui améliore l'expérience.

Performances de la page

Passer d'un prototype fonctionnel à une version sans à-coups signifie passer de l'hypothèse à savoir ce qui se passe dans le navigateur. C'est là que les outils pour les développeurs Chrome sont les plus utiles.

Nous avons passé un temps considérable à optimiser le site. Forcer l'accélération matérielle est bien sûr l'un des outils les plus importants pour obtenir des animations fluides. Vous pouvez également consulter les colonnes colorées et les rectangles rouges dans les outils pour les développeurs Chrome. Il existe de nombreux articles intéressants sur ces sujets. Nous vous conseillons de tous les lire . Le fait de supprimer des images qui sautent est récompensé immédiatement, mais il en va de même lorsqu'ils reviennent. Et c'est le cas. C'est un processus continu qui nécessite des itérations.

J'aime utiliser TweenMax de Greensock pour les propriétés d'interpolation, les transformations et le CSS. Pensez en conteneurs et visualisez votre structure à mesure que vous ajoutez des calques. N'oubliez pas que les transformations existantes peuvent être écrasées par de nouvelles transformations. La translationZ(0) qui a forcé l'accélération matérielle dans votre classe CSS est remplacée par une matrice 2D si vous n'entrez que les valeurs 2D. Dans ce cas, pour que le calque reste en mode accélération, utilisez la propriété "force3D:true" dans l'interpolation afin de créer une matrice 3D au lieu d'une matrice 2D. Il est facile de l'oublier lorsque vous combinez des tweens CSS et JavaScript pour définir des styles.

Ne forcez pas l'accélération matérielle lorsque cela n'est pas nécessaire. La mémoire GPU peut se remplir rapidement et produire des résultats indésirables lorsque vous souhaitez accélérer le matériel de nombreux conteneurs, en particulier sur iOS, où la mémoire est plus limitée. Nous avons apporté d'importantes améliorations pour charger des éléments plus petits, les augmenter à l'aide du code CSS et désactiver certains effets en mode mobile.

Les fuites de mémoire étaient un autre domaine dans lequel nous devions améliorer nos compétences. Lorsque l'on navigue entre les différents WebGL, il y a beaucoup d'objets, de matériaux, de textures et de géométries qui sont créés. Si ceux-ci ne sont pas prêts pour la récupération de mémoire lorsque vous quittez la page et supprimez la section, ils risquent de planter au bout d'un certain temps, lorsque la mémoire est insuffisante.

Sortie d&#39;une section avec une fonction de suppression défaillante.
Sortie d'une section avec une fonction de suppression défaillante.
Bien mieux !
Bien meilleure !

Pour identifier la fuite, il s'agissait d'un workflow assez simple dans les outils de développement : l'enregistrement de la chronologie et la capture d'instantanés de segments de mémoire. Il est plus facile de filtrer des objets spécifiques, comme des géométries 3D ou une bibliothèque spécifique. Dans l'exemple ci-dessus, il s'avère que la scène en 3D était toujours disponible et qu'un tableau contenant la géométrie stockée n'a pas été effacé. S'il vous est difficile de localiser l'objet, il existe une fonctionnalité intéressante qui vous permet de visualiser ce point, appelé tracés de conservation. Il vous suffit de cliquer sur l'objet que vous souhaitez inspecter dans l'instantané du segment de mémoire pour obtenir les informations correspondantes dans un panneau ci-dessous. L'utilisation d'une bonne structure avec des objets plus petits facilite la localisation de vos références.

La scène a été référencée dans EffectComposer.
La scène a été référencée dans EffectComposer.

En général, il est judicieux de bien réfléchir avant de manipuler le DOM. Lorsque vous le faites, pensez à l'efficacité. Si possible, ne manipulez pas le DOM à l'intérieur d'une boucle de jeu. Stockez les références dans des variables pour les réutiliser. Si vous devez rechercher un élément, utilisez l'itinéraire le plus court en stockant des références à des conteneurs stratégiques et en recherchant l'élément ancêtre le plus proche.

Retardez la lecture des dimensions des éléments nouvellement ajoutés, ou lors de la suppression ou de l'ajout de classes si vous rencontrez des bugs de mise en page. Ou assurez-vous que la mise en page est déclenchée. Il arrive que le traitement par lot du navigateur change en styles et qu'il ne soit pas mis à jour après le prochain déclencheur de mise en page. Cela peut vraiment être un gros problème, mais cela a une raison. Essayez donc d'apprendre comment cela fonctionne en arrière-plan et vous y gagnerez beaucoup.

Plein écran

Lorsque cette option est disponible, vous avez la possibilité de placer le site en mode plein écran dans le menu via l'API Fullscreen. Toutefois, sur les appareils, les navigateurs choisissent également de l'afficher en plein écran. Safari sur iOS avait auparavant une astuce pour vous permettre de contrôler cette fonctionnalité, mais cette fonctionnalité n'est plus disponible. Vous devez donc préparer votre conception pour qu'elle fonctionne sans lorsque vous créez une page sans défilement. Nous pouvons probablement nous attendre à des mises à jour à ce sujet dans les prochaines mises à jour, car de nombreuses applications Web sont tombées en panne.

Éléments

Instructions animées pour les tests.
Instructions animées pour les tests

Le site propose de nombreux types d'éléments différents : des images (PNG et JPEG), des images SVG (intégrées et arrière-plans), des feuilles de sprites (PNG), des polices d'icône personnalisées et des animations Adobe Edge. Nous utilisons des fichiers PNG pour les éléments et les animations (feuilles de sprites) lorsque l'élément ne peut pas être basé sur des vecteurs. Dans le cas contraire, nous essayons d'utiliser autant de fichiers SVG que possible.

Le format vectoriel n'entraîne aucune perte de qualité, même en cas de mise à l'échelle. Un fichier pour tous les appareils.

  • Fichier de petite taille.
  • Nous pouvons animer chaque partie séparément (idéal pour les animations avancées). Par exemple, nous masquons le "sous-titre" du logo Hobbit (la désolation de Smaug) lorsqu'il est réduit.
  • Elle peut être intégrée en tant que balise HTML SVG ou utilisée comme image de fond sans chargement supplémentaire (elle est chargée en même temps que la page HTML).

Les polices de caractères d'icônes présentent les mêmes avantages que le format SVG en termes d'évolutivité. Elles sont utilisées à la place du format SVG pour les petits éléments tels que les icônes sur lesquelles il suffit de modifier la couleur (survol, élément actif, etc.). Les icônes sont également très faciles à réutiliser. Il vous suffit de définir la propriété CSS "content" d'un élément.

Animations

Dans certains cas, l'animation d'éléments SVG avec du code peut prendre beaucoup de temps, en particulier lorsque l'animation doit être modifiée régulièrement au cours du processus de conception. Afin d'améliorer le flux de travail entre les concepteurs et les développeurs, nous utilisons Adobe Edge pour certaines animations (les instructions avant les jeux). Le flux de travail d'animation est très proche de Flash et a aidé l'équipe, mais il présente quelques inconvénients, notamment en ce qui concerne l'intégration des animations Edge dans le processus de chargement des éléments, car elle est fournie avec ses propres chargeurs et logique d'implémentation.

Je pense encore qu'il nous reste un long chemin à parcourir avant de parvenir à un workflow parfait pour gérer les éléments et les animations artisanales sur le Web. Nous avons hâte de voir comment des outils comme Edge vont évoluer. N'hésitez pas à ajouter des suggestions sur d'autres outils d'animation et workflows dans les commentaires.

Conclusion

Maintenant que toutes les parties du projet sont disponibles et que nous observons le résultat final, je dois dire que nous sommes très impressionnés par l'état des navigateurs mobiles modernes. Lorsque nous avons commencé ce projet, nous avions beaucoup moins d'attentes quant à la fluidité, l'intégration et les performances que nous pouvions y concrétiser. Cette expérience d'apprentissage a été une excellente expérience, et le temps passé à itérer et à tester (beaucoup) nous a permis de mieux comprendre le fonctionnement des navigateurs modernes. Et c'est ce qu'il faudra si nous voulons réduire le temps de production de ces types de projets, en passant de l'estimation à la connaissance.