Le Frontal de la Terre du Milieu

Présentation du développement multi-appareil

Daniel Isaksson
Daniel Isaksson

Dans notre premier article sur le développement de l'expérience Chrome Un voyage dans la Terre du Milieu, nous nous sommes concentrés sur le développement WebGL pour les appareils mobiles. Dans cet article, nous allons aborder les défis, les problèmes et les solutions que nous avons rencontrés lors de la création du reste du front-end HTML5.

Trois versions d'un même site

Commençons par parler de l'adaptation de ce test pour qu'il fonctionne à la fois sur les ordinateurs de bureau et les appareils mobiles, en fonction de la taille de l'écran et des fonctionnalités de l'appareil.

L'ensemble du projet est basé sur un style très "cinématographique". Sur le plan de la conception, nous avons voulu que l'expérience reste dans un cadre fixe orienté paysage pour préserver la magie du film. Étant donné qu'une grande partie du projet consiste en mini-jeux interactifs, il n'aurait pas non plus de sens de les laisser déborder du cadre.

Prenons la page de destination comme exemple pour voir comment nous adaptons la conception à différentes tailles.

Les aigles nous ont déposés sur la page de destination.
Les aigles nous ont déposés 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 composants chargés au moment de l'exécution et ajouter diverses optimisations des 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 un ensemble de règles définitif.

Nous utilisons les données de l'user-agent pour détecter les appareils mobiles et un test de taille de fenêtre d'affichage pour cibler les tablettes parmi ceux-ci (645 px et plus). En réalité, chaque mode peut afficher toutes les résolutions, car la mise en page est basée sur des requêtes multimédias ou un positionnement relatif/en pourcentage avec JavaScript.

Dans ce cas, les conceptions ne sont pas basées sur des grilles ni sur des règles, et sont très uniques entre les différentes sections. Il dépend donc vraiment de l'élément et du scénario spécifiques des points de rupture ou des styles à utiliser. Il nous est arrivé plus d'une fois d'avoir configuré la mise en page parfaite avec de jolis mixins Sass et des requêtes multimédias, puis d'avoir besoin d'ajouter un effet en fonction de la position de la souris ou d'objets dynamiques, et d'avoir fini par tout réécrire en JavaScript.

Nous ajoutons également une classe avec le mode actuel dans la balise "head" afin de pouvoir utiliser ces informations 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 pixels, ce qui a été très difficile à gérer pour créer une expérience Web immersive. Sur ordinateur, nous définissons une taille minimale avant d'afficher les barres de défilement, car nous souhaitons que vous puissiez profiter du site dans une fenêtre d'affichage plus grande, si possible. Sur les appareils mobiles, nous avons décidé d'autoriser le mode portrait et le mode paysage jusqu'aux expériences interactives, où nous vous demandons de passer en mode paysage. L'argument contre cette décision était qu'il n'était pas aussi immersif en mode portrait qu'en mode paysage. Toutefois, le site s'est bien adapté, et nous l'avons donc conservé.

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'appliquer à tous. Par exemple, vous pouvez prendre en charge la souris et l'écran tactile en même temps. La compensation Retina pour la qualité, mais surtout les performances, est une autre chose. Parfois, une qualité moindre est préférable. Par exemple, le canevas est à moitié de la résolution dans les expériences WebGL sur les écrans Retina, qui devraient autrement afficher quatre fois le nombre de pixels.

Nous avons fréquemment utilisé l'outil d'émulateur dans DevTools 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 la conception. Nous devions toujours effectuer des tests réguliers sur des appareils réels. Cela s'explique par le fait que le site s'adapte en plein écran. Dans la plupart des cas, les pages avec défilement vertical masquent l'interface utilisateur du navigateur lors du défilement (Safari sur iOS7 rencontre actuellement des problèmes à ce sujet), mais nous avons dû tout adapter indépendamment de cela. Nous avons également utilisé un préréglage dans l'émulateur et modifié le paramètre de taille d'é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 atterrissons sur la carte de la Terre du Milieu. Avez-vous remarqué que l'URL a changé ? Il s'agit d'une application à page unique qui utilise l'API History pour gérer le routage.

Chaque section du site est un objet qui hérite d'un ensemble de fonctionnalités telles que les éléments DOM, les transitions, le chargement des composants, la suppression, etc. Lorsque vous explorez différentes parties du site, des sections sont lancées, des éléments sont ajoutés au DOM et en sont supprimés, et les composants 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 avant expiration et les animations doivent être arrêtés et supprimés, sinon ils entraîneront un comportement indésirable, des erreurs et des fuites de mémoire. Ce n'est pas toujours une tâche facile, en particulier lorsque les échéances approchent et que vous devez tout mettre en place le plus rapidement possible.

Présenter les lieux

Pour mettre en valeur les magnifiques décors et les personnages de la Terre du Milieu, nous avons conçu un système modulaire de composants image et texte que vous pouvez faire glisser ou balayer horizontalement. Nous n'avons pas activé de barre de défilement ici, car nous souhaitons avoir des vitesses différentes sur différentes plages, comme dans les séquences d'images où vous arrêtez le mouvement latéral jusqu'à la fin de l'extrait.

Salle de Thranduil
Chronologie de la salle de Thranduil

Chronologie

Au début du développement, nous ne savions pas le contenu des modules pour chaque emplacement. Nous savions que nous voulions un moyen modélisé de présenter différents types de contenus multimédias et d'informations dans une chronologie horizontale qui nous permettrait de présenter six emplacements différents sans avoir à tout reconstruire six fois. Pour gérer cela, nous avons créé un contrôleur de chronologie qui gère le panoramique de ses modules en fonction des paramètres et des comportements des modules.

Modules et composants de comportement

Les différents modules que nous avons ajoutés sont la séquence d'images, l'image fixe, la scène en parallaxe, la scène avec changement de mise au point et le texte.

Le module de scène en parallaxe comporte un arrière-plan opaque avec un nombre personnalisé de calques qui écoutent la progression de la fenêtre d'affichage pour déterminer les positions exactes.

La scène de changement de mise au point est une variante du bucket de parallaxe. En plus, nous utilisons deux images pour chaque calque, qui apparaissent et disparaissent progressivement pour simuler un changement de mise au point. Nous avons essayé d'utiliser le filtre de floutage, mais il est encore trop coûteux. Nous attendrons donc les nuanceurs CSS pour cela.

Le contenu du module de texte peut être déplacé à l'aide du plug-in TweenMax Draggable. Vous pouvez également utiliser la molette de défilement ou balayer l'écran avec deux doigts pour faire défiler l'écran verticalement. Notez le throw-props-plugin qui ajoute la physique de type fling lorsque vous balayez l'écran et le relâchez.

Les modules peuvent également avoir des comportements différents qui sont ajoutés en tant que jeu de composants. Ils disposent tous de leurs propres sélecteurs et paramètres de cible. Fonctionnalité de translation pour déplacer un élément, échelle pour zoomer, points chauds pour la superposition d'informations, métriques de débogage pour les tests visuels, superposition de titre de début, calque de lumière, etc. Ils seront ajoutés au DOM ou contrôleront leur élément cible dans le module.

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

Séquences d'images

La séquence d'images est le module le plus difficile à gérer en termes de performances et de taille de téléchargement. De nombreux articles sont disponibles sur ce sujet. Sur les mobiles et les tablettes, nous les remplaçons par une image fixe. Il s'agit de trop de données à décoder et à stocker en mémoire pour obtenir une qualité correcte sur mobile. Nous avons essayé plusieurs solutions alternatives, en utilisant d'abord une image de fond et une feuille d'éléments, mais cela a entraîné des problèmes de mémoire et des retards lorsque le GPU devait basculer entre les feuilles d'éléments. Nous avons ensuite essayé d'échanger des éléments img, mais cela était également trop lent. Le dessin d'un frame à partir d'une feuille d'éléments graphiques sur un canevas était la méthode la plus performante. Nous avons donc commencé à l'optimiser. Pour économiser du temps de calcul à chaque frame, les données d'image à écrire dans le canevas sont prétraitées via un canevas temporaire et enregistrées dans un tableau avec putImageData(). Elles sont ensuite décodées et prêtes à l'emploi. La feuille d'images d'origine peut ensuite être collectée et nous ne stockons que la quantité minimale de données nécessaire en mémoire. Il est peut-être moins coûteux de stocker des images non décodées, mais nous obtenons de meilleures performances en rembobinant la séquence de cette manière. Les images sont assez petites, seulement 640x400, mais elles ne seront visibles que pendant le balayage. Lorsque vous vous arrêtez, une image haute résolution se charge et apparaît rapidement.

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 d'éléments graphiques de toutes les images d'un dossier.

Animer les modules

Pour placer les modules sur la chronologie, une représentation cachée de la chronologie, affichée en dehors de l'écran, suit le "pointeur de lecture" et la largeur de la chronologie. Cela peut être fait avec du code uniquement, mais une représentation visuelle est utile lors du développement et du débogage. Lors de l'exécution réelle, il est simplement mis à jour lors du redimensionnement pour définir les dimensions. Certains modules remplissent le viewport, tandis que d'autres ont leur propre format. Il a donc été un peu difficile de mettre à l'échelle et de positionner tous les éléments dans toutes les résolutions afin qu'ils soient tous visibles et pas trop recadrés. Chaque module comporte deux indicateurs de progression, l'un pour la position visible à l'écran et l'autre pour la durée du module lui-même. Lorsque vous créez un mouvement de parallaxe, il est souvent difficile de calculer la position de départ et d'arrivée des objets pour les synchroniser avec la position attendue lorsqu'ils sont à l'écran. Il est utile de savoir exactement quand un module entre dans la vue, lit sa chronologie interne et quand il s'anime pour disparaître à nouveau.

Chaque module est surmonté d'une couche noire subtile qui ajuste son opacité pour qu'il soit totalement transparent lorsqu'il est au centre. Vous pouvez ainsi vous concentrer sur un seul 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 la conjecture à la connaissance de ce qui se passe dans le navigateur. C'est là que les outils pour les développeurs Chrome sont vos meilleurs amis.

Nous avons passé beaucoup de temps à optimiser le site. Forcer l'accélération matérielle est l'un des outils les plus importants pour obtenir des animations fluides. Mais aussi à la recherche de colonnes colorées et de rectangles rouges dans Chrome DevTools. Il existe de nombreux articles de qualité sur ces sujets, et vous devriez les tous lire. La récompense pour avoir supprimé les images sautées est immédiate, mais la frustration est tout aussi immédiate lorsqu'elles réapparaissent. Et ils le feront. Il s'agit d'un processus continu qui nécessite des itérations.

J'aime utiliser TweenMax de Greensock pour les propriétés de tweening, les transformations et le CSS. Pensez en termes de 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 valeur translateZ(0) qui forçait l'accélération matérielle dans votre classe CSS est remplacée par une matrice 2D si vous utilisez uniquement des valeurs 2D pour l'interpolation. Pour que le calque reste en mode accélération dans ces cas, utilisez la propriété "force3D:true" dans le tween pour 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.

N'appliquez pas l'accélération matérielle là où elle n'est pas nécessaire. La mémoire du GPU peut rapidement se remplir et entraîner des résultats indésirables lorsque vous souhaitez accélérer matériellement de nombreux conteneurs, en particulier sur iOS, où la mémoire est plus limitée. Le chargement de composants plus petits, leur mise à l'échelle avec CSS et la désactivation de certains effets en mode mobile ont permis d'améliorer considérablement les performances.

Les fuites de mémoire étaient un autre domaine dans lequel nous devions améliorer nos compétences. Lorsque vous passez d'une expérience WebGL à une autre, de nombreux objets, matériaux, textures et géométries sont créés. Si ces éléments ne sont pas prêts à être collectés lorsque vous quittez la section et la supprimez, ils entraîneront probablement un plantage de l'appareil au bout d'un certain temps, lorsqu'il sera à court de mémoire.

Quitter une section avec une fonction de suppression défaillante.
Arrêt d'une section avec une fonction de suppression défaillante.
C&#39;est beaucoup mieux !
C'est beaucoup mieux !

Pour trouver la fuite, le workflow dans les outils de développement était assez simple : enregistrer la chronologie et capturer des instantanés de tas. Il est plus facile de filtrer des objets spécifiques, comme une géométrie 3D ou une bibliothèque spécifique. Dans l'exemple ci-dessus, il s'est avéré que la scène 3D était toujours présente et qu'un tableau qui stockait la géométrie n'avait pas été effacé. Si vous avez du mal à trouver l'emplacement de l'objet, une fonctionnalité pratique appelée parcours de rétention vous permet de le voir. Il vous suffit de cliquer sur l'objet que vous souhaitez inspecter dans l'instantané de tas de mémoire, et les informations s'affichent dans un panneau en dessous. Une bonne structure avec des objets plus petits vous aide à trouver 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 règle générale, il est recommandé de réfléchir à deux fois avant de manipuler le DOM. Pensez à l'efficacité. Si possible, ne manipulez pas le DOM dans une boucle de jeu. Stockez les références dans des variables pour les réutiliser. Si vous devez rechercher un élément, utilisez le chemin le plus court en stockant des références à des conteneurs stratégiques et en effectuant une recherche dans l'élément ancêtre le plus proche.

Retardez la lecture des dimensions des éléments nouvellement ajoutés ou lors de la suppression/l'ajout de classes si vous rencontrez des bugs de mise en page. Vous pouvez également vous assurer que La mise en page est déclenchée. Il arrive que le lot du navigateur change de style et ne soit pas mis à jour après le prochain déclencheur de mise en page. Cela peut parfois être un gros problème, mais il y a une raison pour que cela existe. Essayez donc d'apprendre comment cela fonctionne en coulisses. Vous en retirerez beaucoup.

Plein écran

Lorsque l'option est disponible, vous pouvez mettre le site en mode plein écran dans le menu via l'API Plein écran. Toutefois, sur les appareils, les navigateurs peuvent également décider de l'afficher en plein écran. Safari sur iOS proposait auparavant un hack pour vous permettre de le contrôler, mais il n'est plus disponible. Vous devez donc préparer votre conception pour qu'elle fonctionne sans lui lorsque vous créez une page sans défilement. Nous devrions probablement en savoir plus à ce sujet dans les prochaines mises à jour, car ce problème a affecté de nombreuses applications Web.

Éléments

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

Le site utilise de nombreux types d'assets : images (PNG et JPEG), SVG (en ligne et en arrière-plan), feuilles d'éléments (PNG), polices d'icônes personnalisées et animations Adobe Edge. Nous utilisons des fichiers PNG pour les éléments et les animations (feuilles d'éléments) lorsque l'élément ne peut pas être basé sur des vecteurs. Sinon, nous essayons d'utiliser des fichiers SVG autant que possible.

Le format vectoriel ne présente aucune perte de qualité, même si nous l'adaptons. 1 fichier pour tous les appareils.

  • Taille de fichier réduite.
  • 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.
  • Il peut être intégré en tant que balise HTML SVG ou utilisé en tant qu'image de fond sans chargement supplémentaire (il est chargé en même temps que la page HTML).

Les polices d'icônes présentent les mêmes avantages que les SVG en termes de scalabilité. Elles sont utilisées à la place des SVG pour les petits éléments tels que les icônes, dont nous n'avons besoin que de pouvoir changer la couleur (pointeur, 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 être très chronophage, en particulier lorsque l'animation doit être modifiée de manière importante au cours du processus de conception. Pour améliorer le workflow entre les concepteurs et les développeurs, nous utilisons Adobe Edge pour certaines animations (les instructions avant les jeux). Le workflow d'animation est très proche de Flash, ce qui a aidé l'équipe. Toutefois, il présente quelques inconvénients, en particulier lors de l'intégration des animations Edge dans notre processus de chargement d'éléments, car il est fourni avec ses propres chargeurs et sa logique d'implémentation.

Je pense que nous avons encore un long chemin à parcourir avant de disposer d'un workflow parfait pour gérer les éléments et les animations créées manuellement 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 et flux de travail d'animation dans les commentaires.

Conclusion

Maintenant que toutes les parties du projet sont publiées et que nous examinons 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 des attentes beaucoup plus faibles en termes de fluidité, d'intégration et de performances. Cela a été une excellente expérience d'apprentissage pour nous. Tout le temps passé à itérer et à tester (beaucoup) nous a permis de mieux comprendre le fonctionnement des navigateurs modernes. C'est ce qu'il faudra pour réduire le temps de production de ce type de projets, en passant de la conjecture à la connaissance.