Il front-end della Terra di Mezzo

Procedura dettagliata per lo sviluppo multi-dispositivo

Daniel Isaksson
Daniel Isaksson
Einar Öberg
Einar Öberg

Nel nostro primo articolo sullo sviluppo dell'esperimento di Chrome Un viaggio nella Terra di Mezzo ci siamo concentrati sullo sviluppo di WebGL per i dispositivi mobili. In questo articolo vengono descritte le sfide, i problemi e le soluzioni che abbiamo riscontrato durante la creazione del resto del front-end HTML5.

Tre versioni dello stesso sito

Iniziamo parlando un po' di come adattare questo esperimento per farlo funzionare sia su computer che su dispositivi mobili dal punto di vista delle dimensioni dello schermo e delle funzionalità del dispositivo.

L'intero progetto si basa su uno stile molto "cinematografico", per cui dal punto di vista del design abbiamo voluto mantenere l'esperienza all'interno di un frame fisso in formato orizzontale per preservare la magia del film. Poiché una parte significativa del progetto è costituita da mini "giochi" interattivi, non avrebbe senso lasciarli fuoriuscire dal riquadro.

Possiamo prendere la pagina di destinazione come esempio di come adattiamo il design a dimensioni diverse.

Le aquile ci hanno appena scaricato sulla pagina di destinazione.
Gli aerei ci hanno appena scaricati sulla pagina di destinazione.

Il sito ha tre diverse modalità: desktop, tablet e mobile. Non solo per gestire il layout, ma perché dobbiamo gestire gli asset caricati in fase di runtime e aggiungere varie ottimizzazioni del rendimento. Con i dispositivi che hanno una risoluzione più elevata rispetto ai computer e ai laptop, ma prestazioni inferiori rispetto agli smartphone, non è facile definire l'insieme definitivo di regole.

Utilizziamo i dati dell'user agent per rilevare i dispositivi mobili e un test delle dimensioni della visualizzazione per scegliere come target i tablet (da 645 pixel in su). Ogni modalità diversa può infatti visualizzare tutte le risoluzioni, perché il layout si basa su query sui media o sul posizionamento relativo/in percentuale con JavaScript.

Poiché in questo caso i design non si basano su griglie o regole e sono piuttosto unici tra le diverse sezioni, dipende molto dall'elemento e dallo scenario specifici quali interruzioni o stili utilizzare. Più di una volta abbiamo configurato il layout perfetto con sass-mixin e media-query, poi abbiamo dovuto aggiungere un effetto in base alla posizione del mouse o a oggetti dinamici e abbiamo finito per riscrivere tutto in JavaScript.

Aggiungiamo anche una classe con la modalità corrente nel tag head per poter utilizzare queste informazioni nei nostri stili, come in questo esempio (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.
   }
 }
}

Supportiamo tutte le dimensioni fino a circa 360 x 320, il che è stato piuttosto impegnativo per creare un'esperienza web immersiva. Sul computer, abbiamo una dimensione minima prima di mostrare le barre di scorrimento perché vogliamo che tu possa utilizzare il sito in un'area visibile più grande, se possibile. Sui dispositivi mobili abbiamo deciso di consentire sia la modalità Orizzontale che Ritratto fino alle esperienze interattive, per le quali ti chiediamo di impostare il dispositivo in orizzontale. L'argomento contro questa scelta era che l'esperienza non è così immersiva in verticale come in orizzontale, ma il sito si è adattato abbastanza bene, quindi abbiamo mantenuto questa opzione.

È importante notare che il layout non deve essere confuso con il rilevamento di funzionalità come il tipo di input, l'orientamento del dispositivo, i sensori e così via. Queste funzionalità possono esistere in tutte queste modalità e devono essere presenti in tutte. Un esempio è il supporto del mouse e del tocco contemporaneamente. La compensazione della retina per la qualità, ma soprattutto per il rendimento, è un altro aspetto, a volte una qualità inferiore è migliore. Ad esempio, la tela ha la metà della risoluzione nelle esperienze WebGL sui display Retina, che altrimenti dovrebbero eseguire il rendering quattro volte il numero di pixel

Durante lo sviluppo abbiamo utilizzato di frequente lo strumento di emulazione in DevTools, in particolare in Chrome Canary, che offre nuove funzionalità migliorate e molti preset. È un buon modo per convalidare rapidamente il design. Dovevamo comunque eseguire regolarmente test su dispositivi reali. Uno dei motivi è che il sito si sta adattando al fullscreen. Le pagine con scorrimento verticale nascondono l'interfaccia utente del browser durante lo scorrimento nella maggior parte dei casi (Safari su iOS 7 ha attualmente problemi in merito), ma abbiamo dovuto adattare tutto indipendentemente da questo. Abbiamo anche utilizzato una preimpostazione nell'emulatore e modificato l'impostazione delle dimensioni dello schermo per simulare la perdita di spazio disponibile. I test su dispositivi reali sono importanti anche per monitorare il consumo di memoria e le prestazioni

Gestione dello stato

Dopo la pagina di destinazione, si apre la mappa della Terra di Mezzo. Hai notato la modifica dell'URL? Il sito è un'applicazione a pagina singola che utilizza l'API History per gestire il routing.

Ogni sezione del sito è un oggetto autonomo che eredita un boilerplate di funzionalità come elementi DOM, transizioni, caricamento di asset, eliminazione e così via. Quando esplori parti diverse del sito, le sezioni vengono avviate, gli elementi vengono aggiunti e rimossi dal DOM e gli asset per la sezione corrente vengono caricati.

Poiché l'utente può premere il pulsante Indietro del browser o navigare nel menu in qualsiasi momento, tutto ciò che viene creato deve essere eliminato a un certo punto. I timeout e le animazioni devono essere interrotti e ignorati, altrimenti causeranno comportamenti indesiderati, errori e perdite di memoria. Non è sempre un'impresa facile, soprattutto quando le scadenze si avvicinano e devi inserire tutto il più rapidamente possibile.

Mostrare le sedi

Per mettere in evidenza le splendide ambientazioni e i personaggi della Terra di Mezzo, abbiamo creato un sistema modulare di componenti di testo e immagini che puoi trascinare o scorrere orizzontalmente. Non abbiamo attivato una barra di scorrimento perché vogliamo avere velocità diverse in intervalli diversi, ad esempio nelle sequenze di immagini in cui fermi il movimento laterale fino al termine della riproduzione del clip.

Sala di Thranduil
Tempistiche della Sala di Thranduil

La cronologia

All'inizio dello sviluppo non conoscevamo i contenuti dei moduli per ogni località. Sapevamo che volevamo un modo basato su modelli per mostrare diversi tipi di contenuti multimediali e informazioni in una sequenza temporale orizzontale che ci desse la libertà di avere sei diverse presentazioni delle sedi senza dover ricostruire tutto sei volte. Per gestire questo aspetto, abbiamo creato un controller della sequenza temporale che gestisce la panoramica dei relativi moduli in base alle impostazioni e ai comportamenti dei moduli.

Moduli e componenti di comportamento

I diversi moduli per i quali abbiamo aggiunto il supporto sono sequenza di immagini, immagine fissa, scena con parallasse, scena con cambio di messa a fuoco e testo.

Il modulo della scena con parallasse ha uno sfondo opaco con un numero personalizzato di livelli che ascoltano l'avanzamento dell'area visibile per le posizioni esatte.

La scena con cambio di messa a fuoco è una variante del bucket di parallasse, con l'aggiunta di due immagini per ogni livello che si attenuano e si attenuano per simulare un cambio di messa a fuoco. Abbiamo provato a utilizzare il filtro di sfocatura, ma è ancora troppo costoso, quindi aspetteremo gli shader CSS.

I contenuti del modulo di testo sono attivati per il trascinamento con il plug-in TweenMax Draggable. Puoi anche utilizzare la rotellina di scorrimento o scorrere con due dita per scorrere verticalmente. Tieni presente il plug-in throw-props che aggiunge la fisica stile fling quando scorri e rilasci.

I moduli possono anche avere comportamenti diversi che vengono aggiunti come insieme di componenti. Hanno tutti i propri selettori e impostazioni di destinazione. Traslazione per spostare un elemento, scala per lo zoom, hotspot per la sovrapposizione di informazioni, metriche di debug per i test visivi, una sovrapposizione del titolo iniziale, un livello di sfarfallio e altro ancora. Questi verranno aggiunti al DOM o controlleranno l'elemento di destinazione all'interno del modulo.

In questo modo, possiamo creare le diverse posizioni con un solo file di configurazione che definisce quali asset caricare e configurare i diversi tipi di moduli e componenti.

Sequenze di immagini

Il modulo più impegnativo dal punto di vista delle prestazioni e delle dimensioni del download è la sequenza di immagini. Puoi trovare molte informazioni su questo argomento. Sui dispositivi mobili e sui tablet, la sostituiamo con un'immagine statica. Sono troppi dati da decodificare e memorizzare in memoria se vogliamo una qualità decente sui dispositivi mobili. Abbiamo provato diverse soluzioni alternative: inizialmente abbiamo utilizzato un'immagine di sfondo e uno spritesheet, ma abbiamo riscontrato problemi di memoria e ritardi quando la GPU doveva passare da uno spritesheet all'altro. Poi abbiamo provato a scambiare gli elementi img, ma anche questa soluzione era troppo lenta. Il disegno di un frame da uno spritesheet in una tela aveva il rendimento migliore, quindi abbiamo iniziato a ottimizzarlo. Per risparmiare tempo di calcolo in ogni frame, i dati dell'immagine da scrivere nella tela vengono pre-elaborati tramite una tela temporanea e salvati con putImageData() in un array, decodificati e pronti per l'uso. Lo spritesheet originale può quindi essere sottoposto a garbage collection e memorizziamo solo la quantità minima di dati necessaria in memoria. Forse è addirittura meno costoso memorizzare le immagini non decodificate, ma otteniamo un rendimento migliore durante la scansione della sequenza in questo modo. I fotogrammi sono piuttosto piccoli, solo 640 x 400, ma saranno visibili solo durante la scansione. Quando ti fermi, viene caricata un'immagine ad alta risoluzione che si attenua rapidamente.

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

Gli sprite sheet vengono generati con Imagemagick. Ecco un semplice esempio su GitHub che mostra come creare uno spritesheet di tutte le immagini all'interno di una cartella.

Animazione dei moduli

Per posizionare i moduli nella sequenza temporale, una rappresentazione nascosta della sequenza temporale, visualizzata fuori dallo schermo, tiene traccia del cursore di riproduzione e della larghezza della sequenza temporale. Questo può essere fatto solo con il codice, ma è utile avere una rappresentazione visiva durante lo sviluppo e il debug. Quando viene eseguito per la reale, viene aggiornato solo al ridimensionamento per impostare le dimensioni. Alcuni moduli riempiono la visualizzazione e altri hanno un proprio rapporto, quindi è stato un po' complicato ridimensionare e posizionare tutto in tutte le risoluzioni in modo che tutto sia visibile e non troppo ritagliato. Ogni modulo ha due indicatori di avanzamento, uno per la posizione visibile sullo schermo e uno per la durata del modulo stesso. Quando si crea un movimento parallattico, è spesso difficile calcolare la posizione iniziale e finale degli oggetti da sincronizzare con la posizione prevista quando sono visibili. È bene sapere esattamente quando un modulo entra nella visualizzazione, riproduce la sua sequenza temporale interna e quando scompare di nuovo.

Ogni modulo ha un sottile livello nero in alto che regola la sua opacità in modo che sia completamente trasparente quando è in posizione centrale. In questo modo puoi concentrarti su un modulo alla volta, migliorando l'esperienza.

Rendimento della pagina

Passare da un prototipo funzionante a una versione di release senza scatti significa passare dal fare supposizioni a sapere cosa succede nel browser. È qui che Chrome DevTools è il tuo migliore amico.

Abbiamo impiegato molto tempo per ottimizzare il sito. L'applicazione forzata dell'accelerazione hardware è uno degli strumenti più importanti per ottenere animazioni fluide. ma anche alla ricerca di colonne colorate e rettangoli rossi in Chrome DevTools. Esistono molti articoli validi sugli argomenti e dovresti leggerli tutti. Il premio per la rimozione dei fotogrammi saltati è immediato, ma lo è anche la frustrazione quando si ripresentano. E lo faranno. Si tratta di un processo continuo che richiede iterazioni.

Mi piace utilizzare TweenMax di Greensock per il tweening di proprietà, trasformazioni e CSS. Pensa in termini di contenitori, visualizza la struttura man mano che aggiungi nuovi livelli. Tieni presente che le trasformazioni esistenti possono essere sovrascritte da nuove trasformazioni. Il valore translateZ(0) che forzava l'accelerazione hardware nella classe CSS viene sostituito da una matrice 2D se esegui il tweening solo di valori 2D. Per mantenere il livello in modalità di accelerazione in questi casi, utilizza la proprietà "force3D:true" nel tween per creare una matrice 3D anziché 2D. È facile dimenticarlo quando combini le transizioni CSS e JavaScript per impostare gli stili.

Non forzare l'accelerazione hardware se non è necessaria. La memoria della GPU può riempirsi rapidamente e causare risultati indesiderati quando vuoi accelerare tramite hardware molti contenitori, in particolare su iOS, dove la memoria ha più vincoli. Il caricamento di asset più piccoli, la loro ridimensionamento con CSS e la disattivazione di alcuni effetti in modalità mobile hanno apportato enormi miglioramenti.

Le perdite di memoria erano un altro campo in cui dovevamo migliorare le nostre competenze. Quando navighi tra le diverse esperienze WebGL, vengono creati molti oggetti, materiali, texture e geometrie. Se non sono pronti per la raccolta dei rifiuti quando esci e rimuovi la sezione, probabilmente causeranno l'arresto anomalo del dispositivo dopo un po' di tempo quando esaurirà la memoria.

Uscita da una sezione con una funzione di eliminazione non riuscita.
Uscire da una sezione con una funzione di eliminazione non riuscita.
Molto meglio.
Molto meglio!

Per trovare la perdita, il flusso di lavoro in DevTools è stato abbastanza semplice: registrazione della sequenza temporale e acquisizione di snapshot dell'heap. È più facile se ci sono oggetti specifici, come la geometria 3D o una raccolta specifica, che puoi escludere. Nell'esempio precedente è emerso che la scena 3D era ancora presente e che un array che memorizzava la geometria non era stato cancellato. Se hai difficoltà a individuare la posizione dell'oggetto, esiste una funzionalità utile che ti consente di visualizzarla, chiamata percorsi di conservazione. Basta fare clic sull'oggetto da ispezionare nello snapshot dell'heap per visualizzare le informazioni in un riquadro di seguito. L'utilizzo di una buona struttura con oggetti più piccoli è utile per individuare i riferimenti.

È stato fatto riferimento alla scena in EffectComposer.
È stato fatto riferimento alla scena in EffectComposer.

In generale, è bene riflettere bene prima di manipolare il DOM. In questo caso, pensa all'efficienza. Se puoi, evita di manipolare il DOM all'interno di un ciclo di gioco. Memorizza i riferimenti in variabili per il riutilizzo. Se devi cercare un elemento, utilizza il percorso più breve memorizzando i riferimenti a contenitori strategici e cercando all'interno dell'elemento principale più vicino.

Ritarda la lettura delle dimensioni degli elementi appena aggiunti o quando rimuovi/aggiungi classi se riscontri bug di layout. In alternativa, assicurati che l'opzione Layout sia attivata. A volte il browser applica modifiche collettive agli stili e non si aggiorna dopo l'attivazione del layout successivo. A volte può essere un problema serio, ma esiste per un motivo, quindi cerca di capire come funzionano le cose dietro le quinte e avrai un grande vantaggio.

Schermo intero

Se disponibile, puoi impostare il sito in modalità a schermo intero nel menu tramite l'API Fullscreen. Ma sui dispositivi è anche la decisione dei browser di metterli a schermo intero. In precedenza, Safari su iOS aveva un hack che ti consentiva di controllarlo, ma non è più disponibile, quindi devi preparare il tuo design in modo che funzioni senza questo hack quando crei una pagina non scorrevole. Probabilmente ci aspettiamo aggiornamenti in merito in futuri aggiornamenti, poiché ha causato il malfunzionamento di molte app web.

Asset

Istruzioni animate per gli esperimenti.
Istruzioni animate per gli esperimenti.

Nel sito sono presenti molti tipi diversi di asset: utilizziamo immagini (PNG e JPEG), SVG (in linea e di sfondo), spritesheet (PNG), caratteri icona personalizzati e animazioni Adobe Edge. Utilizziamo i file PNG per gli asset e le animazioni (spritesheet) in cui l'elemento non può essere basato su vettori, altrimenti cerchiamo di utilizzare gli SVG il più possibile.

Il formato vettoriale non comporta alcuna perdita di qualità, anche se lo ridimensioniamo. 1 file per tutti i dispositivi.

  • Dimensioni del file ridotte.
  • Possiamo animare ogni parte separatamente (perfetto per le animazioni avanzate). Ad esempio, nascondiamo il "sottotitolo" del logo de Il Signore degli Anelli (la desolazione di Smaug) quando viene ridotto di dimensioni.
  • Può essere incorporato come tag HTML SVG o utilizzato come sfondo senza caricamenti aggiuntivi (viene caricato contemporaneamente alla pagina HTML).

I caratteri tipografici delle icone hanno gli stessi vantaggi degli SVG in termini di scalabilità e vengono utilizzati al posto degli SVG per piccoli elementi come le icone di cui dobbiamo solo poter cambiare il colore (stato attivo, al passaggio del mouse e così via). Le icone sono anche molto facili da riutilizzare, basta impostare la proprietà CSS "content" di un elemento.

Animazioni

In alcuni casi, l'animazione degli elementi SVG con il codice può richiedere molto tempo, soprattutto se l'animazione deve essere modificata molto durante il processo di progettazione. Per migliorare il flusso di lavoro tra designer e sviluppatori, utilizziamo Adobe Edge per alcune animazioni (le istruzioni prima dei giochi). Il flusso di lavoro di animazione è molto simile a quello di Flash e questo ha aiutato il team, ma ci sono alcuni inconvenienti, in particolare con l'integrazione delle animazioni di Edge nel nostro processo di caricamento delle risorse, poiché sono dotate di propri caricatori e logica di implementazione.

Credo che ci sia ancora molto da fare prima di avere un flusso di lavoro perfetto per la gestione di asset e animazioni create a mano sul web. Non vediamo l'ora di scoprire come si evolveranno strumenti come Edge. Non esitare ad aggiungere suggerimenti su altri strumenti e flussi di lavoro di animazione nei commenti.

Conclusione

Ora che tutte le parti del progetto sono state rilasciate e osserviamo il risultato finale, devo dire che siamo molto impressionati dallo stato dei browser mobile moderni. Quando abbiamo iniziato questo progetto, le nostre aspettative erano molto più basse in termini di fluidità, integrazione e prestazioni. È stata un'esperienza di apprendimento molto importante per noi e tutto il tempo dedicato all'iterazione e ai test (molto) ci ha permesso di comprendere meglio il funzionamento dei browser moderni. Ed è ciò che ci occorre se vogliamo ridurre i tempi di produzione di questi tipi di progetti, passando dalle supposizioni alla conoscenza.