Acquisizione di un'immagine dell'utente

La maggior parte dei browser ha accesso alla fotocamera dell'utente.

Bilance per tappetini

Molti browser ora hanno la possibilità di accedere all'input video e audio dell'utente. Tuttavia, a seconda del browser, potrebbe trattarsi di un'esperienza completa, dinamica e in linea oppure delegata a un'altra app sul dispositivo dell'utente. Inoltre, non tutti i dispositivi hanno nemmeno una fotocamera. Come si crea un'esperienza che utilizzi un'immagine generata dall'utente che funzioni bene ovunque?

Inizia in modo semplice e progressivo

Per migliorare progressivamente la tua esperienza, devi iniziare con qualcosa che funzioni ovunque. La cosa più semplice da fare è semplicemente chiedere all'utente un file preregistrato.

Richiedi un URL

Questa è la migliore opzione supportata, ma meno soddisfacente. Chiedi all'utente di fornirti un URL, quindi utilizzalo. La sola visualizzazione dell'immagine funziona ovunque. Crea un elemento img, imposta l'elemento src e il gioco è fatto.

Tuttavia, se vuoi manipolare l'immagine in qualsiasi modo, le cose sono un po' più complesse. CORS ti impedisce di accedere ai pixel effettivi a meno che il server non imposti le intestazioni appropriate e tu contrassegna l'immagine come multiorigine; l'unico modo pratico è eseguire un server proxy.

Input file

Puoi anche utilizzare un semplice elemento di input del file, incluso un filtro accept che indica che vuoi solo i file immagine.

<input type="file" accept="image/*" />

Questo metodo funziona su tutte le piattaforme. Su desktop chiederà all'utente di caricare un file immagine dal file system. In Chrome e Safari su iOS e Android, questo metodo offre all'utente la possibilità di scegliere quale app utilizzare per acquisire l'immagine, inclusa la possibilità di scattare una foto direttamente con la fotocamera o di scegliere un file immagine esistente.

Un menu Android con due opzioni: acquisizione di immagini e file Un menu iOS con tre opzioni: scatto foto, raccolta fotografica, iCloud

I dati possono quindi essere associati a un <form> o manipolati con JavaScript ascoltando un evento onchange sull'elemento di input e quindi leggendo la proprietà files dell'evento target.

<input type="file" accept="image/*" id="file-input" />
<script>
  const fileInput = document.getElementById('file-input');

  fileInput.addEventListener('change', (e) =>
    doSomethingWithFiles(e.target.files),
  );
</script>

La proprietà files è un oggetto FileList, di cui parleremo più avanti.

Facoltativamente, puoi aggiungere all'elemento l'attributo capture, che indica al browser che preferisci ottenere un'immagine dalla fotocamera.

<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />

Aggiungendo l'attributo capture senza alcun valore, il browser può decidere quale fotocamera utilizzare, mentre i valori "user" e "environment" indicano al browser di preferire rispettivamente le fotocamere anteriore e posteriore.

L'attributo capture funziona su Android e iOS, ma viene ignorato sui computer. Tuttavia, tieni presente che su Android l'utente non avrà più la possibilità di scegliere un'immagine esistente. L'app della fotocamera di sistema verrà avviata direttamente.

Trascina

Se hai già aggiunto la possibilità di caricare un file, ci sono un paio di semplici modi per rendere l'esperienza utente un po' più ricca.

Il primo consiste nell'aggiungere alla pagina una destinazione che consenta all'utente di trascinare un file dal desktop o da un'altra applicazione.

<div id="target">You can drag an image file here</div>
<script>
  const target = document.getElementById('target');

  target.addEventListener('drop', (e) => {
    e.stopPropagation();
    e.preventDefault();

    doSomethingWithFiles(e.dataTransfer.files);
  });

  target.addEventListener('dragover', (e) => {
    e.stopPropagation();
    e.preventDefault();

    e.dataTransfer.dropEffect = 'copy';
  });
</script>

Analogamente all'input del file, puoi ottenere un oggetto FileList dalla proprietà dataTransfer.files dell'evento drop;

Il gestore di eventi dragover consente di segnalare all'utente cosa succederà quando elimina il file utilizzando la proprietà dropEffect.

Il trascinamento è attivo da molto tempo ed è ben supportato dai principali browser.

Incolla dagli appunti

L'ultimo modo per ottenere un file immagine esistente è utilizzare gli appunti. Il codice è molto semplice, ma l'esperienza utente è un po' più difficile.

<textarea id="target">Paste an image here</textarea>
<script>
  const target = document.getElementById('target');

  target.addEventListener('paste', (e) => {
    e.preventDefault();
    doSomethingWithFiles(e.clipboardData.files);
  });
</script>

(e.clipboardData.files è ancora un altro oggetto FileList).

L'aspetto più difficile dell'API per gli appunti è che, per un supporto cross-browser completo, l'elemento target deve essere selezionabile e modificabile. <textarea> e <input type="text"> sono entrambi idonei, così come gli elementi con l'attributo contenteditable. Ma ovviamente sono pensati anche per la modifica del testo.

Può essere difficile far funzionare questa operazione senza problemi se non vuoi che l'utente sia in grado di inserire testo. Trucchi come l'utilizzo di un input nascosto che viene selezionato quando fai clic su un altro elemento potrebbero rendere più difficile il mantenimento dell'accessibilità.

Gestione di un oggetto FileList

Poiché la maggior parte dei metodi precedenti produce un FileList, dovrei parlare un po' di cosa si tratta.

Un FileList è simile a un Array. Include chiavi numeriche e una proprietà length, ma non è effettivamente un array. Non esistono metodi array, come forEach() o pop(), e non è iterabile. Ovviamente puoi ottenere un array reale usando Array.from(fileList).

Le voci del campo FileList sono oggetti File. Sono esattamente uguali agli oggetti Blob, tranne per il fatto che hanno proprietà aggiuntive name e lastModified di sola lettura.

<img id="output" />
<script>
  const output = document.getElementById('output');

  function doSomethingWithFiles(fileList) {
    let file = null;

    for (let i = 0; i < fileList.length; i++) {
      if (fileList[i].type.match(/^image\//)) {
        file = fileList[i];
        break;
      }
    }

    if (file !== null) {
      output.src = URL.createObjectURL(file);
    }
  }
</script>

Questo esempio trova il primo file con un tipo MIME immagine, ma potrebbe anche gestire più immagini che vengono selezionate/incollate/tralasciate contemporaneamente.

Una volta ottenuto l'accesso al file, puoi fare tutto ciò che vuoi. Ad esempio, puoi:

  • Trasformalo in un elemento <canvas> in modo da poterlo modificare
  • Scaricala sul dispositivo dell'utente
  • Caricalo su un server con fetch()

Accedi alla fotocamera in modo interattivo

Ora che hai delineato le basi, è il momento di migliorare progressivamente.

I browser moderni possono accedere direttamente alle fotocamere, in modo da creare esperienze completamente integrate con la pagina web in modo che l'utente non debba mai uscire dal browser.

Ottenere l'accesso alla fotocamera

Puoi accedere direttamente a una videocamera e a un microfono utilizzando un'API nella specifica WebRTC chiamata getUserMedia(). In questo modo, all'utente verrà chiesto di accedere ai microfoni e alle videocamere connessi.

Il supporto per getUserMedia() è piuttosto buono, ma non è ancora disponibile dappertutto. In particolare, non è disponibile in Safari 10 o versioni precedenti, che al momento della scrittura è ancora l'ultima versione stabile. Tuttavia, Apple ha annunciato che sarà disponibile su Safari 11.

Tuttavia, è molto semplice rilevare il supporto.

const supported = 'mediaDevices' in navigator;

Quando chiami getUserMedia(), devi inserire un oggetto che descrive il tipo di contenuto multimediale che vuoi. Queste scelte sono chiamate vincoli. Esistono diversi limiti possibili, che riguardano ad esempio la preferenza per la fotocamera anteriore o posteriore, l'audio e la risoluzione che preferisci per lo stream.

Tuttavia, per acquisire i dati dalla videocamera, è sufficiente un solo vincolo, video: true.

Se l'operazione ha esito positivo, l'API restituirà un MediaStream contenente i dati della fotocamera e potrai associarlo a un elemento <video> e riprodurlo per mostrare un'anteprima in tempo reale oppure associarlo a un <canvas> per ottenere uno snapshot.

<video id="player" controls autoplay></video>
<script>
  const player = document.getElementById('player');

  const constraints = {
    video: true,
  };

  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    player.srcObject = stream;
  });
</script>

Di per sé, non è molto utile. Tutto ciò che puoi fare è recuperare i dati del video e riprodurli. Se vuoi ottenere un'immagine, devi fare un po' di lavoro in più.

Scatta una foto

L'opzione più supportata per ottenere un'immagine è disegnare un fotogramma del video su una tela.

A differenza dell'API Web Audio, non esiste un'API di elaborazione dello stream dedicata per i video sul web, quindi è necessario ricorrere a un piccolo pirateria informatica per acquisire un'istantanea dalla videocamera dell'utente.

La procedura è la seguente:

  1. Crea un oggetto canvas che sostenga l'inquadratura della fotocamera
  2. Ottieni l'accesso allo stream della videocamera
  3. Allegarlo a un elemento video
  4. Se vuoi acquisire un fotogramma preciso, aggiungi i dati dell'elemento video a un oggetto canvas utilizzando drawImage().
<video id="player" controls autoplay></video>
<button id="capture">Capture</button>
<canvas id="canvas" width="320" height="240"></canvas>
<script>
  const player = document.getElementById('player');
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const captureButton = document.getElementById('capture');

  const constraints = {
    video: true,
  };

  captureButton.addEventListener('click', () => {
    // Draw the video frame to the canvas.
    context.drawImage(player, 0, 0, canvas.width, canvas.height);
  });

  // Attach the video stream to the video element and autoplay.
  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    player.srcObject = stream;
  });
</script>

Una volta archiviati i dati della fotocamera sulla tela, puoi fare molte cose. Cosa puoi fare:

  • Caricalo direttamente sul server
  • Archiviazione locale
  • Applica effetti originali all'immagine

Suggerimenti

Interrompi lo streaming dalla videocamera quando non è necessario

È buona norma interrompere l'uso della videocamera quando non è più necessario. Ciò non solo consente di risparmiare batteria e potenza di elaborazione, ma consente anche agli utenti di fidarsi di più della tua applicazione.

Per interrompere l'accesso alla videocamera, puoi semplicemente chiamare stop() su ogni traccia video per lo stream restituito da getUserMedia().

<video id="player" controls autoplay></video>
<button id="capture">Capture</button>
<canvas id="canvas" width="320" height="240"></canvas>
<script>
  const player = document.getElementById('player');
  const canvas = document.getElementById('canvas');
  const context = canvas.getContext('2d');
  const captureButton = document.getElementById('capture');

  const constraints = {
    video: true,
  };

  captureButton.addEventListener('click', () => {
    context.drawImage(player, 0, 0, canvas.width, canvas.height);

    // Stop all video streams.
    player.srcObject.getVideoTracks().forEach(track => track.stop());
  });

  navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
    // Attach the video stream to the video element and autoplay.
    player.srcObject = stream;
  });
</script>

Chiedi l'autorizzazione per usare la videocamera in modo responsabile

Se l'utente non ha precedentemente concesso al tuo sito l'accesso alla videocamera, nell'istante in cui chiami getUserMedia(), il browser chiederà all'utente di concedere l'autorizzazione del sito alla fotocamera.

Gli utenti odiano ricevere la richiesta di accesso a dispositivi potenti sulla loro macchina e bloccheranno spesso la richiesta oppure la ignoreranno se non comprendono il contesto per cui è stata creata. Come best practice, ti consigliamo di chiedere di accedere alla videocamera solo quando è la prima volta che ne hai bisogno. Una volta che l'utente ha concesso l'accesso, non gli verrà più chiesto di farlo. Tuttavia, se l'utente rifiuta l'accesso, non potrai più accedervi, a meno che l'utente non modifichi manualmente le impostazioni di autorizzazione della fotocamera.

Compatibilità

Ulteriori informazioni sull'implementazione dei browser per dispositivi mobili e desktop:

Ti consigliamo inoltre di utilizzare lo shim adapter.js per proteggere le app da modifiche alle specifiche WebRTC e differenze nei prefissi.

Feedback