Schritt-für-Schritt-Anleitung für die Entwicklung für verschiedene Geräte
In unserem ersten Artikel zur Entwicklung des Chrome-Experiments Eine Reise durch Mittelerde haben wir uns auf die WebGL-Entwicklung für Mobilgeräte konzentriert. In diesem Artikel gehen wir auf die Herausforderungen, Probleme und Lösungen ein, die wir beim Erstellen des restlichen HTML5-Front-Ends gemeistert haben.
Drei Versionen derselben Website
Zuerst wollen wir uns damit befassen, wie dieser Test sowohl auf Computern als auch auf Mobilgeräten funktioniert, was Bildschirmgröße und Gerätefunktionen angeht.
Das gesamte Projekt basiert auf einem sehr „filmischen“ Stil. Wir wollten das Design in einem horizontal ausgerichteten, festen Frame halten, um die Magie des Films zu bewahren. Da ein großer Teil des Projekts aus interaktiven Minispielen besteht, wäre es auch nicht sinnvoll, sie über den Frame hinausragen zu lassen.
Anhand der Landingpage können wir sehen, wie wir das Design an verschiedene Größen anpassen.
Die Website hat drei verschiedene Modi: Computer, Tablet und Mobilgerät. Nicht nur, um das Layout zu verarbeiten, sondern auch, weil wir Laufzeit geladene Assets verarbeiten und verschiedene Leistungsoptimierungen hinzufügen müssen. Bei Geräten mit einer höheren Auflösung als Desktop-Computer und Laptops, aber einer schlechteren Leistung als Smartphones, ist es nicht einfach, eine endgültige Reihe von Regeln zu definieren.
Wir verwenden User-Agent-Daten, um Mobilgeräte zu erkennen, und einen Test der Ansichtsgröße, um Tablets (645 px und höher) anzusprechen. Jeder Modus kann tatsächlich alle Auflösungen rendern, da das Layout auf Media-Queries oder relativer/prozentualer Positionierung mit JavaScript basiert.
Da die Designs in diesem Fall nicht auf Rastern oder Regeln basieren und sich die einzelnen Abschnitte stark voneinander unterscheiden, hängt es wirklich vom jeweiligen Element und Szenario ab, welche Breakpoints oder Stile verwendet werden sollen. Es kam mehr als einmal vor, dass wir das perfekte Layout mit schönen Sass-Mixins und Media-Queries eingerichtet hatten und dann einen Effekt basierend auf der Mausposition oder dynamischen Objekten hinzufügen mussten und am Ende alles in JavaScript neu geschrieben haben.
Wir fügen dem Head-Tag auch eine Klasse mit dem aktuellen Modus hinzu, damit wir diese Informationen in unseren Stilen verwenden können, wie in diesem Beispiel (in 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.
}
}
}
Wir unterstützen alle Größen bis etwa 360 × 320 Pixel, was die Entwicklung eines immersiven Weberlebnisses ziemlich schwierig macht. Auf dem Computer wird die Bildlaufleiste erst angezeigt, wenn die Website eine bestimmte Mindestgröße erreicht hat. Wir möchten, dass Sie die Website nach Möglichkeit in einem größeren Darstellungsbereich sehen. Auf Mobilgeräten ist sowohl das Hoch- als auch das Querformat zulässig, bis hin zu den interaktiven Inhalten, bei denen Sie das Gerät ins Querformat drehen müssen. Das Argument dagegen war, dass das Hochformat nicht so immersiv ist wie das Querformat. Die Website skalierte jedoch ziemlich gut, sodass wir sie beibehalten haben.
Das Layout darf nicht mit der Funktionserkennung wie Eingabetyp, Geräteausrichtung, Sensoren usw. verwechselt werden. Diese Funktionen können in allen diesen Modi vorhanden sein und sollten alle umfassen. Ein Beispiel ist die gleichzeitige Unterstützung von Maus und Touchbedienung. Die Retina-Kompensation für Qualität, aber vor allem für Leistung ist ein weiteres Beispiel. Manchmal ist eine geringere Qualität besser. Beispielsweise hat das Canvas in WebGL-Anwendungen auf Retina-Displays nur die Hälfte der Auflösung, da sonst viermal so viele Pixel gerendert werden müssten.
Während der Entwicklung haben wir häufig das Emulator-Tool in den Entwicklertools verwendet, insbesondere in Chrome Canary, das neue verbesserte Funktionen und viele Voreinstellungen bietet. Es ist eine gute Möglichkeit, das Design schnell zu validieren. Wir mussten trotzdem regelmäßig auf echten Geräten testen. Ein Grund dafür ist, dass die Website an den Vollbildmodus angepasst wird. Bei Seiten mit vertikalem Scrollen wird die Browseroberfläche beim Scrollen in den meisten Fällen ausgeblendet (Safari auf iOS 7 hat derzeit Probleme damit). Wir mussten jedoch alles unabhängig davon anpassen. Außerdem haben wir im Emulator ein vordefiniertes Design verwendet und die Bildschirmgröße geändert, um den Verlust an verfügbarem Platz zu simulieren. Tests auf echten Geräten sind auch wichtig, um den Arbeitsspeicherverbrauch und die Leistung zu überwachen.
Status verarbeiten
Nach der Landingpage landen wir auf der Karte von Mittelerde. Haben Sie bemerkt, dass sich die URL geändert hat? Die Website ist eine Single-Page-Anwendung, die das Routing mithilfe der History API verarbeitet.
Jeder Bereich der Website ist ein eigenes Objekt, das eine Reihe von Funktionen wie DOM-Elemente, Übergänge, das Laden von Assets und das Entfernen von Assets erbt. Wenn Sie verschiedene Bereiche der Website aufrufen, werden Bereiche gestartet, Elemente dem DOM hinzugefügt und daraus entfernt und Assets für den aktuellen Bereich geladen.
Da der Nutzer jederzeit die Rückwärtsschaltfläche des Browsers drücken oder über das Menü navigieren kann, muss alles, was erstellt wird, irgendwann entfernt werden. Zeitüberschreitungen und Animationen müssen beendet und verworfen werden, da sie sonst zu unerwünschtem Verhalten, Fehlern und Speicherlecks führen. Das ist nicht immer einfach, vor allem wenn Abgabetermine näher rücken und Sie alles so schnell wie möglich fertigstellen müssen.
Standorte präsentieren
Um die wunderschönen Kulissen und Charaktere von Mittelerde zu präsentieren, haben wir ein modulares System aus Bild- und Textkomponenten entwickelt, die Sie horizontal verschieben oder wischen können. Wir haben hier keine Bildlaufleiste aktiviert, da wir für verschiedene Bereiche unterschiedliche Geschwindigkeiten haben möchten, z. B. bei Bildsequenzen, bei denen die Bewegung seitlich angehalten wird, bis der Clip abgespielt ist.
Zeitachse
Zu Beginn der Entwicklung wussten wir nicht, wie die Inhalte der Module für die einzelnen Standorte aussehen würden. Wir wussten, dass wir eine Vorlage für die Darstellung verschiedener Medien- und Informationstypen in einer horizontalen Zeitachse wollten, die uns die Freiheit gibt, sechs verschiedene Standortpräsentationen zu haben, ohne alles sechsmal neu erstellen zu müssen. Dazu haben wir einen Zeitleisten-Controller erstellt, der das Schwenken der Module basierend auf den Einstellungen und dem Verhalten der Module steuert.
Module und Verhaltenskomponenten
Die verschiedenen Module, für die wir die Unterstützung hinzugefügt haben, sind Bildsequenz, Standbild, Parallaxe-Szene, Fokuswechsel-Szene und Text.
Das Modul „Parallax-Szene“ hat einen opaken Hintergrund mit einer benutzerdefinierten Anzahl von Ebenen, die den Fortschritt des Darstellungsbereichs für genaue Positionen überwachen.
Die Fokusverschiebungsszene ist eine Variante des Parallaxe-Buckets. Der Unterschied besteht darin, dass wir für jede Ebene zwei Bilder verwenden, die ein- und ausgeblendet werden, um eine Fokusänderung zu simulieren. Wir haben versucht, den Weichzeichner zu verwenden, aber er ist immer noch zu teuer. Wir warten also auf CSS-Shader.
Der Inhalt im Textmodul kann mit dem TweenMax-Plug-in Draggable per Drag-and-drop verschoben werden. Sie können auch das Scrollrad oder Wischen mit zwei Fingern verwenden, um vertikal zu scrollen. Beachten Sie das throw-props-plugin, das die Physik des Fliegens hinzufügt, wenn Sie wischen und loslassen.
Die Module können auch unterschiedliche Verhaltensweisen haben, die als Komponenten hinzugefügt werden. Sie haben jeweils eigene Zielauswahlen und -einstellungen. Du kannst Elemente verschieben, skalieren, Hotspots für Info-Overlays hinzufügen, Debug-Messwerte für visuelle Tests verwenden, einen Starttitel-Overlay und eine Flare-Ebene einfügen und vieles mehr. Diese werden dem DOM angehängt oder steuern das Zielelement innerhalb des Moduls.
So können wir die verschiedenen Standorte mit nur einer Konfigurationsdatei erstellen, in der festgelegt wird, welche Assets geladen und die verschiedenen Arten von Modulen und Komponenten eingerichtet werden sollen.
Bildsequenzen
Das schwierigste Modul in Bezug auf Leistung und Downloadgröße ist die Bildsequenz. Hier finden Sie weitere Informationen zu diesem Thema. Auf Mobilgeräten und Tablets wird es durch ein Standbild ersetzt. Das sind zu viele Daten, um sie zu decodieren und im Arbeitsspeicher zu speichern, wenn wir eine anständige Qualität auf Mobilgeräten erreichen möchten. Wir haben mehrere alternative Lösungen ausprobiert, zuerst ein Hintergrundbild und ein Spritesheet, aber das führte zu Speicherproblemen und Verzögerungen, wenn die GPU zwischen den Spritesheets wechseln musste. Dann haben wir versucht, die img-Elemente auszutauschen, aber das war auch zu langsam. Das Zeichnen eines Frames aus einem Spritesheet auf eine Leinwand war am leistungsstärksten, also haben wir damit begonnen, das zu optimieren. Um die Rechenzeit pro Frame zu sparen, werden die Bilddaten, die in den Canvas geschrieben werden sollen, über einen temporären Canvas vorverarbeitet und mit putImageData() in einem Array gespeichert, decodiert und für die Verwendung vorbereitet. Das ursprüngliche Spritesheet kann dann durch den Garbage Collector gelöscht werden und wir speichern nur die minimale Menge an Daten im Arbeitsspeicher. Vielleicht ist es tatsächlich weniger, nicht decodierte Bilder zu speichern, aber wir erzielen so eine bessere Leistung beim Durchsuchen der Sequenz. Die Frames sind ziemlich klein, nur 640 × 400 Pixel, aber sie sind nur beim Vor- und Zurückspulen sichtbar. Wenn Sie anhalten, wird ein hochauflösendes Bild geladen und schnell eingeblendet.
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++;
}
}
Die Sprite-Sheets werden mit Imagemagick generiert. Hier finden Sie ein einfaches Beispiel auf GitHub, das zeigt, wie Sie ein Spritesheet mit allen Bildern in einem Ordner erstellen.
Module animieren
Um die Module auf der Zeitachse zu platzieren, wird eine ausgeblendete Darstellung der Zeitachse außerhalb des Bildschirms angezeigt, die den „Wiedergabestandort“ und die Breite der Zeitachse verfolgt. Das ist auch mit reinem Code möglich, aber eine visuelle Darstellung ist bei der Entwicklung und Fehlerbehebung hilfreich. Bei der tatsächlichen Ausführung wird es nur bei der Größenänderung aktualisiert, um die Abmessungen festzulegen. Einige Module füllen den Darstellungsbereich aus, andere haben ein eigenes Seitenverhältnis. Daher war es etwas schwierig, alles in allen Auflösungen so zu skalieren und zu positionieren, dass alles sichtbar ist und nicht zu stark zugeschnitten wird. Jedes Modul hat zwei Fortschrittsanzeigen: eine für die sichtbare Position auf dem Bildschirm und eine für die Dauer des Moduls. Bei der Erstellung von Parallaxe-Bewegungen ist es oft schwierig, die Start- und Endposition von Objekten zu berechnen, um sie mit der erwarteten Position zu synchronisieren, wenn sie im Blickfeld sind. Es ist wichtig zu wissen, wann ein Modul in den Bereich eintritt, seine interne Zeitleiste abspielt und wieder aus dem Blickfeld verschwindet.
Jedes Modul hat eine dezente schwarze Schicht, deren Deckkraft so angepasst wird, dass sie in der Mitte vollständig transparent ist. So können Sie sich jeweils auf ein Modul konzentrieren, was die Nutzung erleichtert.
Seitenleistung
Wenn Sie von einem funktionierenden Prototyp zu einer ruckelfreien Releaseversion wechseln, bedeutet das, dass Sie nicht mehr nur Vermutungen anstellen, sondern genau wissen, was im Browser passiert. Hier kommen die Chrome-Entwicklertools ins Spiel.
Wir haben viel Zeit in die Optimierung der Website investiert. Die Hardwarebeschleunigung zu erzwingen, ist natürlich eines der wichtigsten Tools, um flüssige Animationen zu erzielen. Aber auch nach bunten Spalten und roten Rechtecken in den Chrome-Entwicklertools. Es gibt viele gute Artikel zu den Themen und Sie sollten alle lesen. Das Entfernen von Frames, die übersprungen werden, ist zwar sofort spürbar, aber auch die Frustration, wenn sie wiederkommen. Und das werden sie auch. Es ist ein fortlaufender Prozess, der Iterationen erfordert.
Ich verwende gerne TweenMax von Greensock für Tweening-Eigenschaften, Transformationen und CSS. Denken Sie in Containern und visualisieren Sie Ihre Struktur, während Sie neue Ebenen hinzufügen. Beachten Sie, dass vorhandene Transformationen durch neue Transformationen überschrieben werden können. Die translateZ(0), die die Hardwarebeschleunigung in Ihrer CSS-Klasse erzwungen hat, wird durch eine 2D-Matrix ersetzt, wenn Sie nur 2D-Werte tweenen. Wenn Sie die Ebene in diesen Fällen im Beschleunigungsmodus lassen möchten, verwenden Sie im Tween die Property „force3D:true“, um eine 3D-Matrix anstelle einer 2D-Matrix zu erstellen. Das kann leicht vergessen werden, wenn Sie CSS- und JavaScript-Tweens kombinieren, um Stile festzulegen.
Erzwingen Sie die Hardwarebeschleunigung nicht, wenn sie nicht erforderlich ist. Der GPU-Speicher kann sich schnell füllen und zu unerwünschten Ergebnissen führen, wenn Sie viele Container hardwarebeschleunigen möchten, insbesondere unter iOS, wo der Speicher stärker eingeschränkt ist. Wir haben kleinere Assets geladen, sie mit CSS skaliert und einige der Effekte im mobilen Modus deaktiviert. Dadurch konnten wir große Verbesserungen erzielen.
Speicherlecks waren ein weiteres Gebiet, in dem wir uns verbessern mussten. Wenn Sie zwischen den verschiedenen WebGL-Umgebungen wechseln, werden viele Objekte, Materialien, Texturen und Geometrien erstellt. Wenn diese nicht für die Garbage Collection bereit sind, wenn Sie den Bereich verlassen und entfernen, führt dies wahrscheinlich dazu, dass das Gerät nach einiger Zeit abstürzt, wenn der Arbeitsspeicher aufgebraucht ist.
Um den Speicherleck zu finden, war der Workflow in den DevTools ziemlich einfach: Ich habe die Zeitachse aufgezeichnet und Heap-Snapshots erfasst. Es ist einfacher, wenn es bestimmte Objekte wie 3D-Geometrie oder eine bestimmte Bibliothek gibt, die Sie herausfiltern können. Im obigen Beispiel stellte sich heraus, dass die 3D-Szene noch vorhanden war und auch ein Array, in dem Geometrie gespeichert wurde, nicht gelöscht wurde. Wenn Sie das Objekt nicht finden können, gibt es eine praktische Funktion namens Begrenzungspfade, mit der Sie das Objekt aufrufen können. Klicken Sie einfach auf das Objekt, das Sie im Heap-Snapshot prüfen möchten. Die Informationen werden dann in einem Bereich darunter angezeigt. Eine gute Struktur mit kleineren Objekten hilft beim Auffinden Ihrer Referenzen.
Generell ist es ratsam, zweimal nachzudenken, bevor Sie das DOM manipulieren. Denken Sie dabei an Effizienz. Bearbeiten Sie das DOM innerhalb einer Gameloop nach Möglichkeit nicht. Speichern Sie Verweise zur Wiederverwendung in Variablen. Wenn Sie nach einem Element suchen müssen, verwenden Sie die kürzeste Route. Speichern Sie dazu Verweise auf strategische Container und suchen Sie im nächsten übergeordneten Element.
Verzögern Sie das Lesen von Dimensionen neu hinzugefügter Elemente oder das Entfernen/Hinzufügen von Klassen, wenn Layoutfehler auftreten. Prüfen Sie auch, ob das Layout ausgelöst wird. Manchmal ändert der Browser-Batch die Stile und wird nach dem nächsten Layout-Trigger nicht aktualisiert. Das kann manchmal ein großes Problem sein, aber es gibt einen Grund dafür. Versuchen Sie also, herauszufinden, wie es hinter den Kulissen funktioniert. Das wird Ihnen sehr helfen.
Vollbild
Sofern verfügbar, kannst du die Website über die Fullscreen API im Menü in den Vollbildmodus versetzen. Auf Geräten kann die Vollbildansicht aber auch vom Browser festgelegt werden. Unter iOS gab es in Safari früher einen Hack, mit dem Sie das steuern konnten. Dieser ist jedoch nicht mehr verfügbar. Wenn Sie also eine Seite ohne Scrollen erstellen, müssen Sie Ihr Design so vorbereiten, dass es ohne diesen Hack funktioniert. Wir können wahrscheinlich in zukünftigen Updates weitere Informationen dazu erwarten, da viele Webanwendungen dadurch nicht mehr funktionieren.
Assets
Auf der Website werden viele verschiedene Arten von Assets verwendet, darunter Bilder (PNG und JPEG), SVG (Inline und Hintergrund), Spritesheets (PNG), benutzerdefinierte Symbolschriften und Adobe Edge-Animationen. Wir verwenden PNGs für Assets und Animationen (Spritesheets), bei denen das Element nicht vektorbasiert sein kann. Andernfalls versuchen wir, nach Möglichkeit SVGs zu verwenden.
Das Vektorformat bedeutet keinen Qualitätsverlust, auch wenn wir es skalieren. 1 Datei für alle Geräte
- Kleine Dateigröße.
- Wir können jeden Teil separat animieren (perfekt für erweiterte Animationen). Als Beispiel wird der „Untertitel“ des Hobbit-Logos (die Verwüstung von Smaug) ausgeblendet, wenn es verkleinert wird.
- Es kann als SVG-HTML-Tag eingebettet oder als Hintergrundbild ohne zusätzliches Laden verwendet werden (es wird gleichzeitig mit der HTML-Seite geladen).
Symbolschriften bieten die gleichen Vorteile wie SVG in Bezug auf die Skalierbarkeit und werden anstelle von SVG für kleine Elemente wie Symbole verwendet, bei denen nur die Farbe geändert werden muss (z. B. bei Hover- oder Aktivstatus). Die Symbole lassen sich auch sehr einfach wiederverwenden. Sie müssen dazu nur die CSS-Property „content“ eines Elements festlegen.
Animationen
In einigen Fällen kann die Animation von SVG-Elementen mit Code sehr zeitaufwendig sein, insbesondere wenn die Animation während des Designprozesses häufig geändert werden muss. Um den Workflow zwischen Designern und Entwicklern zu verbessern, verwenden wir Adobe Edge für einige Animationen (die Anleitungen vor den Spielen). Der Animationsablauf ähnelt stark dem von Flash, was dem Team sehr geholfen hat. Es gibt jedoch einige Nachteile, insbesondere bei der Einbindung der Edge-Animationen in unseren Asset-Ladeprozess, da es eigene Loader und Implementierungslogik gibt.
Ich glaube, es ist noch ein weiter Weg, bis wir einen perfekten Workflow für die Verwaltung von Assets und handgefertigten Animationen im Web haben. Wir sind gespannt, wie sich Tools wie Edge weiterentwickeln werden. Du kannst in den Kommentaren gerne Vorschläge zu anderen Animationstools und ‑Workflows machen.
Fazit
Jetzt, da alle Teile des Projekts veröffentlicht sind und wir uns das Endergebnis ansehen, muss ich sagen, dass wir ziemlich beeindruckt sind vom Stand der modernen mobilen Browser. Als wir mit diesem Projekt begonnen haben, hatten wir viel niedrigere Erwartungen daran, wie nahtlos, integriert und leistungsfähig wir es gestalten könnten. Das war eine tolle Lernerfahrung für uns. Durch die vielen Iterationen und Tests haben wir unser Verständnis für die Funktionsweise moderner Browser verbessert. Und genau das ist nötig, wenn wir die Produktionszeit bei diesen Arten von Projekten verkürzen möchten, also von Vermutungen zu Gewissheiten gelangen wollen.