Introduzione a PixiJS

In questi ultimi anni le possibilità messe a disposizione per gli sviluppatori Web da parte del W3C sono aumentata a dismisura. Basti pensare alle Web Animations API oppure le Speech Recognition e Speech Synthesis API. Una delle “nuove” API più importanti, secondo chi vi scrive, è WebGL.

In pratica le WebGL sono un porting di OpenGL utilizzabile in ambiente web. La loro potenza sta nel fatto che bypassano completamente il rendering del browser, andando direttamente sulla CPU/GPU. Questo permette di ottenere performance eccezionali altrimenti non raggiungibili in altro modo. Per avere un’idea della potenza di fuoco che questa API mette a disposizione, fatevi un giro sui Chrome Experiments basati su WebGL. Programmare in WebGL però non è semplice e bisogna avere almeno un’infarinatura di concetti legati alla grafica 3D.

Ecco alcuni dei Chrome Experiments con le WebGL

Ecco alcuni dei Chrome Experiments con le WebGL

In nostro soccorso ci sono alcune librerie che ci facilitano la scrittura di applicazioni basate su WebGL. Quella di cui ci occuperemo oggi è PixiJS. Il focus di questa libreria però non è la creazione di contenuti 3D, ma si concentra sul 2D. Per conoscerne meglio le caratteristiche creeremo una semplice applicazione step-by-step con questo framework.

Let’s Code

Iniziamo subito con il codice. Il nostro primo step sarà quello di creare uno spazio vuoto in cui aggiungere i nostri elementi.

import { Application } from 'pixi.js'

const app = new Application(window.innerWidth, window.innerHeight, {backgroundColor: 0x323B44})

document.body.appendChild(app.view)

Come vedete la prima cosa da fare per lavorare con un’applicazione PixiJS è quella di inizializzare un oggetto di tipo Application. Dopo avergli dato le dimensioni (in questo caso tutta la finestra) e un colore di sfondo, basta collegare la sua view al body ed il gioco è fatto.

Applicazione vuota

Applicazione vuota

Non proprio un esempio allettante, ma è quello che io chiamo un Black Triangle! Il nostro prossimo passa quello di visualizzare il nostro logo al centro dello schermo.

Caricamento Assets

In applicazioni ad alto contenuto “grafico” svolgono un ruolo fondamentali gli assets. Il numero di questi assets può essere elevato, in genere molto maggiore rispetto ad una normale web application. PixiJS ci permette di controllare in maniera puntale il caricamento di questi assets, come possiamo vedere nel prossimo esempio.

import { Application, loader, Sprite } from 'pixi.js'

const app = new Application(window.innerWidth, window.innerHeight, {backgroundColor: 0x323B44})

document.body.appendChild(app.view)

const LOGO_URL = 'logo.png'

loader.add(LOGO_URL).load(() => {
  const logo = new Sprite(loader.resources[LOGO_URL].texture)

  logo.x = (app.renderer.width - logo.width) / 2
  logo.y = (app.renderer.height - logo.height) / 2

  app.stage.addChild(logo)
})

Grazie all’oggetto loader siamo in grado di caricare assets in maniera programmatica. I due metodi principali del loader sono add, con il quale possiamo aggiungere elementi alla coda di caricamento, e load con in quale iniziamo il caricamento dei file attualmente in coda. Questo ultimo metodo accetta un ingresso una callback che verrà invocata quando tutti i file saranno caricati. Tutti i file caricati sono poi presenti all’interno del dizionario loader.resources.

Nel caso specifico carichiamo il logo di CNJ, una volta completato il download creiamo uno Sprite che inseriamo perfettamente al centro dello schermo. Notate infine che per aggiungere elementi alla scena dobbiamo passare per la proprietà stage della nostra Application. Lo stage infatti è il container principale di tutti gli elementi della nostra applicazione.

Ecco il nostro primo asset con PixiJS

Ecco il nostro primo asset con PixiJS

Filtri e animazioni

In questo step aggiungeremo la nostra prima animazione. Applicheremo un filtro al nostro logo che varierà di intensità nel tempo.

import { Application, loader, Sprite, filters } from 'pixi.js'

const app = new Application(window.innerWidth, window.innerHeight, {backgroundColor: 0x323B44})

let count = 0

document.body.appendChild(app.view)

const LOGO_URL = 'logo.png'

loader.add(LOGO_URL).load(() => {
  const logo = new Sprite(loader.resources[LOGO_URL].texture)

  logo.x = (app.renderer.width - logo.width) / 2
  logo.y = (app.renderer.height - logo.height) / 2
  logo.filters = [new filters.ColorMatrixFilter()]

  app.ticker.add(() => {
    count += 0.01
    logo.filters[0].greyscale(Math.abs(Math.sin(count)), false)
  })

  app.stage.addChild(logo)
})

Gli Sprite hanno una proprietà filters che ci permette di agganciare una serie di filtri ai nostri elementi. In questo caso ho scelto un ColorMatrixFilter che permette di modificare i colori del nostro Sprite. Potete leggere gli altri filtri disponibili sulla documentazione ufficiale. Osservando il risultato di questo snippet nella gif qui in basso noterete che il logo è in bianco e nero, con delle tonalità che cambiano nel tempo. Questa animazione è facilmente ottenibile aggiungendo una nostra callback al ticker della nostra app. In pratica ogni volta che per PixiJS è possibile eseguire un ciclo di render esegue tutte le callbacks legate al suo ticker. All’interno delle callback dobbiamo quindi modificare i nostri oggetti, in modo che durante il render il sistema stampi qualcosa leggermente differente rispetto al frame precedente. In questo caso cambiano semplicemente il valore del greyscale del nostro filtro.

Ecco il nostro logo animato

Ecco il nostro logo animato

Elementi interattivi

Come tocco finale alla nostra applicazione aggiungiamo una nuova animazione, la quale però verrà attivata/disattivata al click sul nostro logo. Potete provare l’applicazione completa in questa area demo.

import { Application, loader, Sprite, filters } from 'pixi.js'

const app = new Application(window.innerWidth, window.innerHeight, {backgroundColor: 0x323B44})

let count = 0
let shouldRotate = false

document.body.appendChild(app.view)

const LOGO_URL = 'logo.png'

loader.add(LOGO_URL).load(() => {
  const logo = new Sprite(loader.resources[LOGO_URL].texture)

  logo.x = (app.renderer.width - logo.width) / 2
  logo.y = (app.renderer.height - logo.height) / 2

  logo.interactive = true
  logo.on('click', () => { shouldRotate = !shouldRotate })

  logo.filters = [new filters.ColorMatrixFilter()]

  app.ticker.add(() => {
    if (shouldRotate) {
      logo.rotation -= 0.01
    }

    count += 0.01
    logo.filters[0].greyscale(Math.abs(Math.sin(count)), false)
  })

  app.stage.addChild(logo)
})

Per poter interagire con un elemento basta utilizzare il metodo on a cui passare il nome dell’evento a cui rispondere e l’handler desiderato. Unica cosa da tenere a mente è quella di settare la proprietà interactive a true prima di collegare qualsiasi handler. In caso contrario verrano semplicemente ignorati.

Ecco la nostra prima applicazione PixiJS

Ecco la nostra prima applicazione PixiJS

Conclusioni

Con questa piccolissima applicazione avete visto tutti gli elementi cardine di un’applicazione PixiJS. Siete quindi ora in grado di provare a muovere i primi passi con questo framework. Questo framework è particolarmente indicato per videogame per browsers o per siti vetrina particolarmente accattivanti, come potete vedere nella sezione gallery del sito ufficiale. In realtà secondo me c’è un altro caso d’uso interessante che può valere la pena approfondire: il suo utilizzo in applicazioni “enterprise” quando le prestazione standard non bastano. Immaginate ad esempio una lunga lista di dati in un’applicazione Ionic o Electron. In questi casi infatti il rendering del browser a volte mostra i suoi limiti, e poter sfruttare la velocità del rendering WebGL può darvi davvero una mano. A patto ovviamente il budget per poter costruire un layout utilizzando PixiJS al posto del normale HTML + CSS. I più curiosi possono trovare il codice del progetto in questo repository GitHub. Alla Prossima.

Francesco Strazzullo

Faccio il Front-end engineer per extrategy dove mi occupo di applicazioni Angular, React e applicazioni mobile. Da sempre sono appassionato di Architetture software e cerco di applicarle in maniera innovativa allo sviluppo frontend in generale.