Three.js — responsive e viewport resize

Gianluca Lomarco
6 min readJun 4, 2023

--

Per ottenere il massimo dell'immersività dalle nostre scene 3D in three.js sarebbe meglio che queste riempissero tutto lo spazio della viewport adattandosi alle dimensione del dispositivo con cui l'utente visualizzerà la pagina web.

Partendo da quello che abbiamo fatto nell'articolo precedente vediamo come possiamo fare.

Introduzione

Attualmente abbiamo impostato al renderer un dimensione fissa di 1024px di larghezza e 720px di altezza. Questo comporta che se la viewport del browser fosse più piccola di queste dimensioni comparirebbero le scroll bar e la canvas non sarebbe tutta visibile nella pagina.

Adattiamo la canvas alla viewport

Per impostare le dimensioni del render avevamo usato una variabile temp

const temp = {
width: 1024,
height: 720,
}

che modificheremo in questo modo. La rinomineremo e alle proprietà width e height assegneremo le dimensioni della viewport

const sizes = {
width: window.innerWidth,
height: window.innerHeight,
}

ricordiamoci di modificare anche i riferimenti alla variabile temp nella camera e nel renderer

...

const camera = new THREE.PerspectiveCamera(
75,
sizes.width / sizes.height,
0.1,
10
)

...

renderer.setSize(temp.width, temp.height)

...

Ora il render ha le dimensioni della viewport.

Non preoccuparti del margine bianco intorno alla canvas, adesso lo andiamo a rimuovere.

Questo infatti è il margine che di default viene applicato al body dallo user agent stylesheet, che contiene un preset iniziale di stili per molti elementi html.

Per rimuoverlo assicuriamoci di avere un file style.css nella nostra repo e in caso contrario creiamolo.

Ed importiamolo all'inizio del file main.js insieme agli altri import.

import './style.css'
import * as THREE from 'three'
import gsap from 'gsap'

...

A questo punto possiamo scrivere le regole css di reset per rimuovere alcuni degli stili dello user aget, nello specifico rimuoviamo margini e padding da tutti gli elementi ed impostiamo box sizing a border-box che ci puó aiutare nel gestire meglio i padding.

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

Possiamo anche impostare alla canvas una position fixed, ma nel nostro esempio non è necessario. Nel caso avessimo del contenuto html scrollabile allora sarebbe stato necessario per mantenere la scena 3D visibile nella pagina come se fosse uno sfondo.

Nel caso in cui non avessimo del contenuto scrollabile conviene aggiungere questa regola per rimuovere lo scroll dei touch screen.

html, body {
overflow: hidden;
}

Ecco che adesso possiamo godere della nostra scena 3D nel massimo della sua espressività.

Ma c’è un piccolo problema! Se provi a ridimensionare la finestra del browser il nostro render non si adatta alle nuove dimensioni. Risolviamo quest'ultimo problema.

Resize della viewport

Per reagire al resize della viewport dobbiamo ascoltare l'evento resize sulla window. Aggiungiamo quindi un event listener che invochi una funzione quando viene emesso l'evento resize.

window.addEventListener('resize', () => {
...
})

Come primo passaggio aggiorniamo le dimensioni nell'oggetto sizes:

window.addEventListener('resize', () => {
// update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
})

e in seguito aggiorniamo l'aspect ratio della camera altrimenti i nostri render risulterebbero distorti.

window.addEventListener('resize', () => {
...

// update camera
camera.aspect = sizes.width / sizes.height
})

Quando modifichiamo le proprietà della camera come l'aspect ratio dobbiamo anche dire alla camera di aggiornare la matrice di proiezione. Questa matrice infatti viene utilizzata i tutti i vertex shader per poter proiettare i vertici degli oggetti che si trovato nello spazio sul piano immaginario della camera in cui viene generato il render. Approfondiremo questo argomento più avanti.

Per aggiornare la matrice di proiezione dobbiamo invocare il metodo camera.updateProjectionMatrix().

window.addEventListener('resize', () => {
...

// update camera
camera.aspect = sizes.width / sizes.height
// update projection matrix
camera.updateProjectionMatrix()
})

Per ultimo non ci rimane che aggiornare le dimensioni del render invocando il metodo renderer.setSize(…), in questo modo si aggiornerà anche la dimensione della canvas che abbiamo nel DOM.

window.addEventListener('resize', () => {
...

// update renderer
renderer.setSize(sizes.width, sizes.height)

})

Ed ecco il risultato finale:

window.addEventListener('resize', () => {
// update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight

// update camera
camera.aspect = sizes.width / sizes.height
// update projection matrix
camera.updateProjectionMatrix()

// update renderer
renderer.setSize(sizes.width, sizes.height)
})

Provando a ridimensionare le viewport adesso si aggiorneranno anche in nostri render e tutto rimarrà contenuto nella finestra del browser.

Prima di concludere parliamo di un ultimo argomento che in alcuni casi risulterà indispensabile per visualizzare correttamente le nostre scene.

Pixel ratio

Infatti a seconda del dispositivo con cui stai visualizzando la pagina potresti notare le immagini non sono molto definite risultando leggermente frastagliate sui bordi oppure con una leggera sfocatura. Questo è dovuto alla densità di pixel dello schermo che stai utilizzando, l'aspect ratio appunto, che indica quanti pixel fisici ci sono per ogni unità di pixel del browser.

Molti schermi recenti infatti hanno un pixel ratio maggiore di 1, per esempio gli schermi retina, che gli permette di riprodurre immagini molto più definite ed avere risoluzioni più elevate.

In parole povere

Se il nostro monitor ha un pixel ratio di 2, per disegnare un bordo rosso di 1px con il css ci saranno fisicamente 2 pixel colorati di rosso e se la densità di pixel fosse 3 allora i pixel colorati sarebbero 3.

Questo ha chiaramente un impatto sul nostro render in quanto un pixel ratio di 2 vuol dire renderizzare il quadruplo dei pixel, mentre un pixel ratio di 3 vuol dire render 9 volte più grandi.

Possiamo ottenere la densità di pixel leggendo la proprietà window.devicePixelRatio e utilizzarla aggiornando il renderer in questo modo:

renderer.setPixelRatio( window.devicePixelRatio )

Questo potrebbe causare dei rallentamenti della fluidità delle animazioni, soprattutto nei casi in qui la densità di pixel sia maggiore di 2. Sconsiglio di impostare un pixel ratio maggiore di 2 dato che il miglioramento della qualità non è così percettibile ad occhio nudo, mentre probabilmente avremmo un significativo calo di performance.

Quindi modifichiamo leggermente quello che abbiamo appena scritto.

const pixelRatio = Math.min( window.devicePixelRatio, 2 )
renderer.setPixelRatio( pixelRatio )

Usiamo la funzione min(…) dell'oggetto Math per assegnare alla variabile pixelRatio il valore di window.devicePixelRatio nel caso quest'ultimo fosse minore di 2, e 2 in caso contrario.

Possiamo aggiungere queste due righe anche al nostro listener per reagire al cambiamento di pixel ratio del monitor, valido per quegli utenti che hanno più di un monitor che potrebbero avere caratteristiche diverse.

window.addEventListener('resize', () => {
// update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight

// update camera
camera.aspect = sizes.width / sizes.height
// update projection matrix
camera.updateProjectionMatrix()

// update renderer
renderer.setSize(sizes.width, sizes.height)
// update pixel ratio
const pixelRatio = Math.min( window.devicePixelRatio, 2 )
renderer.setPixelRatio( pixelRatio )
})

Conclusioni

Abbiamo visto come adattare le nostre scene alla viewport del browser e reagire al resize di quest'ultima. Così facendo possiamo ottenere il massimo dell'espressività e dell'immersività dalle nostre scene 3D.

Nel prossimo articolo vediamo come controllare la camera tramite i controls, strumenti che three.js ci mette a disposizione per ascoltare gli input dell'utente e muovere la camera.

--

--

Gianluca Lomarco

I am a creative developer working with webGL and fullstack Main teacher at Boolean Academy. Now I’am starting a new experience as content creator in YouTube