Il front-end della Terra di Mezzo

Procedura dettagliata dello sviluppo multi-dispositivo

Daniel Isaksson
Daniel Isaksson
Einar Öberg
Einar Öberg

Nel nostro primo articolo sullo sviluppo dell'esperimento Chrome A Journey Through Middle-earth ci siamo concentrati sullo sviluppo di WebGL per i dispositivi mobili. In questo articolo vengono illustrati le sfide, le soluzioni e i problemi riscontrati durante la creazione del resto del front-end HTML5.

Tre versioni dello stesso sito

Iniziamo parlando un po' di come adattare questo esperimento al funzionamento su computer e dispositivi mobili da un punto di vista delle dimensioni degli schermi e delle funzionalità dei dispositivi.

L'intero progetto è incentrato su uno stile molto "cinematografico", in cui dal punto di vista del design volevamo mantenere l'esperienza all'interno di una cornice fissa orientata in orizzontale per mantenere la magia del film. Poiché gran parte del progetto è costituita da mini "giochi" interattivi, non avrebbe senso nemmeno lasciarli oltrepassare il limite.

La pagina di destinazione potrebbe essere un esempio di come adattiamo il design a dimensioni diverse.

Le aquile ci hanno appena fatto cadere sulla pagina di destinazione.
Le aquile ci hanno appena portato alla pagina di destinazione.

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

Stiamo utilizzando i dati dello user agent per rilevare i dispositivi mobili e un test delle dimensioni dell'area visibile per scegliere come target i tablet tra questi (da 645 px). Ogni modalità diversa può infatti visualizzare tutte le risoluzioni, poiché il layout si basa sulle query supporti o sul posizionamento relativo/percentuale con JavaScript.

Poiché i design in questo caso non si basano su griglie o regole e sono piuttosto unici tra le diverse sezioni, dipende in realtà dall'elemento e dallo scenario specifici in merito ai punti di interruzione o agli stili da utilizzare. Più di una volta abbiamo configurato il layout perfetto con belle sass-mixin e media-query e poi abbiamo dovuto aggiungere un effetto basato sulla posizione del mouse o sugli oggetti dinamici e alla fine abbiamo riscritto tutto in JavaScript.

Aggiungiamo anche una classe con la modalità corrente nel tag head in modo da poter usare questa informazione 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 difficile quando si trattava di un'esperienza web immersiva. Sui computer, abbiamo una dimensione minima prima di mostrare le barre di scorrimento, perché vogliamo che tu possa vivere l'esperienza del 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, in cui ti chiediamo di impostare il dispositivo in orizzontale. L'argomentazione contro la questione era che il luogo non era così immersivo nei ritratti come in orizzontale, ma le dimensioni del sito erano piuttosto buone, quindi l'abbiamo tenuto.

È importante notare che il layout non deve essere confuso con il rilevamento di funzionalità, ad esempio il tipo di input, l'orientamento del dispositivo, i sensori e così via. Queste funzionalità possono esistere in tutte queste modalità e devono estendersi a tutte. Un esempio è il supporto simultaneo di mouse e tocco. La compensazione Retina per la qualità, ma la maggior parte delle prestazioni è un'altra, a volte una qualità inferiore è migliore. Ad esempio, la tela è dimezzata nelle esperienze WebGL sui display Retina, che altrimenti dovrebbero essere visualizzati quattro volte il numero di pixel.

Durante lo sviluppo abbiamo usato spesso l'emulatore in DevTools, soprattutto in Chrome Canary, che offre nuove funzionalità migliorate e molte preimpostazioni. È un buon modo per convalidare rapidamente il design. Dovevamo comunque eseguire regolarmente i test su dispositivi reali. Uno dei motivi è che il sito si sta adattando alla modalità a schermo intero. Le pagine con scorrimento verticale nascondono l'interfaccia utente del browser nella maggior parte dei casi (al momento Safari su iOS7 ha problemi con questa funzionalità), ma dovevamo 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. Eseguire test su dispositivi reali è importante anche per monitorare il consumo di memoria e le prestazioni

Gestione dello stato

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

Ogni sezione del sito è il proprio oggetto che eredita funzionalità standard come elementi DOM, transizioni, caricamento di asset, eliminazione ecc. Quando si esplorano diverse parti del sito, vengono avviate le sezioni, gli elementi vengono aggiunti e rimossi dal DOM e gli asset della sezione corrente vengono caricati.

Poiché l'utente può premere il pulsante Indietro del browser o navigare attraverso il menu in qualsiasi momento, prima o poi tutto ciò che viene creato dovrà essere eliminato. Timeout e animazioni devono essere arrestati ed eliminati, altrimenti causano comportamenti indesiderati, errori e perdite di memoria. Non sempre si tratta di un compito facile, soprattutto quando si avvicinano le scadenze e occorre fare tutto il possibile il più rapidamente possibile.

Metti in mostra le sedi

Per mostrare le meravigliose ambientazioni e i personaggi della Terra di Mezzo, abbiamo creato un sistema modulare di componenti di immagini e testo che puoi trascinare o far scorrere in orizzontale. Non abbiamo attivato una barra di scorrimento qui perché vogliamo avere velocità diverse su intervalli diversi, ad esempio nelle sequenze di immagini in cui il movimento viene interrotto lateralmente fino alla riproduzione del clip.

Sala di Thranduil
Tempistiche di Thranduil's Hall

Cronologia

All'inizio dello sviluppo non conoscevamo i contenuti dei moduli per ogni località. Quello che sapevamo era che volevamo un modo basato su modelli per mostrare diversi tipi di media e informazioni in una sequenza temporale orizzontale che ci dasse la libertà di avere sei diverse presentazioni di luoghi senza dover ricreare tutto sei volte. Per farlo, abbiamo creato un controller della sequenza temporale che gestisce il panning dei moduli in base alle impostazioni e ai comportamenti dei moduli.

Moduli e componenti comportamentali

I diversi moduli per i quali abbiamo aggiunto il supporto sono: sequenza di immagini, immagini fisse, scena di parallasse, scena con spostamento della messa a fuoco e testo.

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

La scena con spostamento della messa a fuoco è una variante del bucket parallasse, a cui vengono aggiunte due immagini per ogni livello che si dissolve in entrata e in uscita per simulare un cambio di messa a fuoco. Abbiamo provato a usare il filtro di sfocatura, ma è ancora troppo costoso, quindi aspettiamo che lo strumento si veda gli Shader CSS.

I contenuti nel modulo di testo possono essere trascinati con il plug-in TweenMax trascinabile. Puoi anche utilizzare la rotellina di scorrimento o scorrere con due dita per scorrere in verticale. Nota il throw-props-plugin che aggiunge le leggi sullo stile di fling quando scorri e rilasci.

I moduli possono anche avere comportamenti diversi aggiunti come insieme di componenti. Ognuno di essi ha i propri selettori e impostazioni di destinazione. Traduci per spostare un elemento, scala per eseguire lo zoom, hotspot per l'overlay di informazioni, metriche di debug per i test visivi, un overlay del titolo iniziale, un livello dei bagliori e altro ancora. Questi elementi verranno aggiunti al DOM o controlleranno l'elemento target all'interno del modulo.

Con questa impostazione possiamo creare le diverse posizioni con un solo file di configurazione che definisce gli asset da caricare e configurare i diversi tipi di moduli e componenti.

Sequenze di immagini

Il più impegnativo dei moduli in termini di performance e dimensioni per il download è la sequenza delle immagini. C'è molto da leggere su questo argomento. Su dispositivi mobili e tablet, la sostituiamo con un'immagine statica. Sono troppi dati da decodificare e memorizzare in memoria se vogliamo una qualità discreta sui dispositivi mobili. Abbiamo provato diverse soluzioni alternative, utilizzando prima un'immagine di sfondo e un foglio sprite, ma questo ha causato problemi di memoria e ritardi quando la GPU doveva passare da un foglio sprite a un altro. Poi abbiamo provato a scambiare gli elementi img, ma era anche troppo lento. Disegnare un frame da un foglio sprite a una tela è stato il risultato migliore, quindi abbiamo iniziato a ottimizzarlo. Per ridurre il tempo di calcolo di ogni frame, i dati di immagine da scrivere sul canvas vengono pre-elaborati tramite un canvas temporaneo e salvati con putImageData() su un array, decodificati e pronti per essere utilizzati. Il foglio sprite originale può quindi essere garbage collection e archivia solo la quantità minima di dati necessari in memoria. Magari l'archiviazione delle immagini non decodificate richiede meno tempo, ma l'esecuzione dello scrubbing della sequenza in questo modo migliora il rendimento. Le cornici sono piuttosto piccole, solo 640 x 400, ma saranno visibili durante lo scrubbing. Quando ti fermi, un'immagine ad alta risoluzione viene caricata e svanisce 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++;
  }
}

I fogli sprite 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 sulla sequenza temporale, una rappresentazione nascosta della sequenza temporale, visualizzata fuori schermo, tiene traccia della "tesina di riproduzione" e della larghezza della sequenza temporale. Questo può essere fatto con il solo codice, ma era una buona rappresentazione visiva durante lo sviluppo e il debug. Quando l'esecuzione è reale, viene aggiornata al momento del ridimensionamento per impostare le dimensioni. Alcuni moduli riempiono l'area visibile e altri hanno le proprie proporzioni, quindi era un po' difficile ridimensionare e posizionare tutto a tutte le risoluzioni, in modo che tutto sia visibile e non ritagliato troppo. Ogni modulo ha due indicatori di avanzamento, uno per la posizione visibile sullo schermo e uno per la durata del modulo stesso. Quando si esegue un movimento parallasse, è spesso difficile calcolare le posizioni di inizio e fine degli oggetti per sincronizzarli con la posizione prevista quando sono nella vista. È utile sapere esattamente quando un modulo entra nella visualizzazione, riproduce la sequenza temporale interna e quando anima di nuovo fuori dalla visualizzazione.

Ogni modulo presenta un sottile strato nero nella parte superiore che ne regola l'opacità in modo che sia completamente trasparente quando è in posizione centrale. Questo ti aiuta a concentrarti su un modulo alla volta, migliorando l'esperienza.

Rendimento delle pagine

Passare da un prototipo funzionante a una versione di release senza jank significa passare dall'ipotesi a sapere cosa succede nel browser. È qui che Chrome DevTools è utile.

Abbiamo dedicato molto tempo all'ottimizzazione del sito. Forzare l'accelerazione hardware è uno degli strumenti più importanti ovviamente per ottenere animazioni fluide. Ma anche cercare colonne colorate e rettangoli rossi in Chrome DevTools. Esistono molti articoli interessanti sugli argomenti e dovresti leggerli tutti. Il vantaggio per la rimozione dei frame ignorati è immediato, ma lo è anche la frustrazione quando tornano. E sì. È un processo continuo che richiede iterazioni.

Mi piace usare TweenMax di Greensock per il tweening di proprietà, trasformazioni e CSS. Pensa ai container, visualizza la tua struttura mentre aggiungi nuovi livelli. Tieni presente che le trasformazioni esistenti possono essere sovrascritte da nuove trasformazioni. Il valore traslaZ(0) che ha forzato l'accelerazione hardware nella classe CSS viene sostituito da una matrice 2D se si utilizzano solo valori 2D. Per mantenere il livello in modalità di accelerazione in questi casi, usa la proprietà "force3D:true" nel tween per creare una matrice 3D anziché una 2D. È facile dimenticarsi quando si combinano i preadolescenti CSS e JavaScript per impostare gli stili.

Non forzare l'accelerazione hardware dove non è necessaria. La memoria della GPU può esaurirsi rapidamente e causare risultati indesiderati quando vuoi accelerare l'hardware di molti container, soprattutto su iOS dove la memoria ha più vincoli. Caricare asset più piccoli, aumentarne lo scale up con CSS e disattivare alcuni effetti in modalità mobile ha apportato enormi miglioramenti.

Le fuga di memoria erano un altro campo in cui dovevamo migliorare le nostre competenze. Quando ci si sposta tra i diversi oggetti WebGL, si creano molti oggetti, materiali, texture e geometria. Se questi elementi non sono pronti per la garbage collection quando esci e rimuovi la sezione, è probabile che il dispositivo si arresti in modo anomalo dopo un po' di tempo quando esaurisce la memoria.

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

L'individuazione della perdita è stato piuttosto semplice in DevTools, con la registrazione della sequenza temporale e l'acquisizione di istantanee dell'heap. È più facile se ci sono oggetti specifici, come la geometria 3D o una libreria specifica, che puoi filtrare. Nell'esempio in alto si è scoperto che la scena 3D era ancora nelle vicinanze e che anche un array con la geometria memorizzata non è stato cancellato. Se hai difficoltà a individuare la posizione dell'oggetto, puoi visualizzare un'utile funzionalità chiamata percorsi di conservazione. Fai clic sull'oggetto che vuoi ispezionare nell'istantanea heap per visualizzare le informazioni in un riquadro di seguito. L'utilizzo di una buona struttura con oggetti più piccoli aiuta a individuare i riferimenti.

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

In generale, è salutare pensarci due volte prima di manipolare il DOM. In questo caso, pensa all'efficienza. Non manipolare il DOM all'interno di un ciclo di gioco se puoi aiutarlo. Archivia i riferimenti nelle variabili per il riutilizzo. Se devi cercare un elemento, utilizza il percorso più breve archiviando i riferimenti ai container strategici e cercando all'interno dell'elemento predecessore più vicino.

ritarda la lettura delle dimensioni degli elementi appena aggiunti o durante la rimozione o l'aggiunta di classi se riscontri bug di layout. In alternativa, verifica che Layout sia attivato. A volte il browser cambia in gruppo gli stili e non viene aggiornato dopo il successivo attivatore del layout. Questo può essere davvero un grosso problema a volte, ma esiste un motivo, quindi cerca di capire come funziona dietro le quinte e guadagnerai molto.

Schermo intero

Se disponibile, hai la possibilità di attivare la modalità a schermo intero del sito nel menu tramite l'API Fullscreen. Sui dispositivi c'è anche la decisione dei browser di metterlo a schermo intero. Safari su iOS in passato disponeva di un attacco che ti consente di controllarlo, ma questa soluzione non è più disponibile, quindi devi preparare il tuo design affinché funzioni a meno quando crei una pagina senza scorrimento. È probabile che ci aspettiamo aggiornamenti su questo argomento nei prossimi aggiornamenti, dato che ha rotto molte app web.

Asset

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

In tutto il sito sono presenti diversi tipi di asset; utilizziamo immagini (PNG e JPEG), SVG (in linea e di sfondo), fogli sprite (PNG), caratteri di icone personalizzate e animazioni Adobe Edge. Utilizziamo i file PNG per gli asset e le animazioni (spritesheet) in cui l'elemento non può essere di tipo vettoriale, altrimenti cerchiamo di utilizzare il più possibile i SVG.

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

  • File di dimensioni ridotte.
  • Possiamo animare ogni parte separatamente (perfetto per animazioni avanzate). Ad esempio, nascondiamo il "sottotitolo" del logo Hobbit (la desolazione di Smaug) quando viene ridimensionato.
  • Può essere incorporata come tag HTML SVG o utilizzata come immagine di sfondo senza ulteriori caricamenti (viene caricata nello stesso momento della pagina html).

I caratteri tipografici delle icone presentano gli stessi vantaggi di SVG in termini di scalabilità e vengono usati al posto di SVG per piccoli elementi come le icone su cui è sufficiente cambiare il colore (passaggio del mouse, attivo, ecc.). Le icone sono anche molto facili da riutilizzare, è sufficiente impostare la proprietà CSS "contenuti" di un elemento.

Animazioni

In alcuni casi l'animazione degli elementi SVG con il codice può richiedere molto tempo, soprattutto quando 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 dell'animazione è molto simile a Flash e questo ha aiutato il team, ma presenta alcuni svantaggi, in particolare l'integrazione delle animazioni Edge nel nostro processo di caricamento degli asset, poiché l'utilizzo è dotato di caricatori e logica di implementazione propri.

Credo che abbiamo ancora molto da fare prima di ottenere un flusso di lavoro perfetto per la gestione degli asset e delle animazioni fatte a mano sul web. Non vediamo l'ora di scoprire come si evolveranno strumenti come Edge. Sentiti libero di aggiungere nei commenti suggerimenti su altri strumenti e flussi di lavoro di animazione.

Conclusione

Ora che vengono rilasciate tutte le parti del progetto e diamo un'occhiata al risultato finale, devo dire che siamo rimasti molto colpiti dallo stato dei moderni browser mobile. All'inizio di questo progetto, le nostre aspettative erano molto inferiori rispetto a quanto avremmo potuto realizzare senza soluzione di continuità, integrazione e prestazioni. È stata una grande esperienza di apprendimento per noi e tutto il tempo dedicato all'iterazione e ai test (molto) ha migliorato la nostra comprensione di come funzionano i browser moderni. E questo è ciò che serve se vogliamo ridurre i tempi di produzione di questo tipo di progetti, dall'incertezza alla conoscenza.