Acquisizione di un'immagine dell'utente

La maggior parte dei browser può accedere alla fotocamera dell'utente.

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 dinamica e in linea completa o potrebbe essere delegata a un'altra app sul dispositivo dell'utente. Inoltre, non tutti i dispositivi sono dotati di fotocamera. Quindi, come puoi creare un'esperienza che utilizzi un'immagine generata dall'utente che funzioni bene ovunque?

Inizia in modo semplice e graduale

Se vuoi migliorare progressivamente la tua esperienza, devi iniziare con qualcosa che funzioni ovunque. Il modo più semplice è chiedere all'utente un file preregistrato.

Chiedere un URL

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

Tuttavia, se vuoi manipolare l'immagine in qualche modo, le cose si complicano un po'. CORS ti impedisce di accedere ai pixel effettivi, a meno che il server non imposti le intestazioni appropriate e tu non contrassegni l'immagine come crossorigin. L'unico modo pratico per aggirare il problema è eseguire un server proxy.

Input file

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

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

Questo metodo funziona su tutte le piattaforme. Sul computer, verrà chiesto all'utente di caricare un file immagine dal file system. In Chrome e Safari su iOS e Android, questo metodo consente all'utente di scegliere l'app da 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: acquisisci immagine e file Un menu iOS con tre opzioni: scatta foto, libreria foto, iCloud

I dati possono quindi essere collegati a un <form> o manipolati con JavaScript ascoltando un evento onchange nell'elemento di immissione e 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 parlerò più avanti.

Facoltativamente, puoi anche aggiungere all'elemento l'attributo capture, che indica al browser che preferisci ricevere un'immagine dalla videocamera.

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

L'aggiunta dell'attributo capture senza un valore consente al browser di decidere quale fotocamera utilizzare, mentre i valori "user" e "environment" indicano al browser di preferire rispettivamente la fotocamera anteriore e posteriore.

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

Trascina

Se aggiungi già la possibilità di caricare un file, esistono un paio di semplici modi per migliorare un po' l'esperienza utente.

La prima consiste nell'aggiungere alla pagina un target di trascinamento che consenta all'utente di trascinare un file dal computer 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 file, puoi ottenere un oggetto FileList dalla proprietà dataTransfer.files dell'evento drop.

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

Il trascinamento è disponibile da molto tempo ed è ampiamente supportato dai principali browser.

Incolla dagli appunti

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

<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 è un altro oggetto FileList).

La parte complicata dell'API della clipboard è che, per un supporto completo su più browser, l'elemento target deve essere sia selezionabile che modificabile. Sia <textarea> che <input type="text"> sono adatti qui, così come gli elementi con l'attributo contenteditable. ma sono ovviamente progettati anche per la modifica del testo.

Può essere difficile far funzionare tutto senza problemi se non vuoi che l'utente possa inserire testo. Trucchi come avere un input nascosto che viene selezionato quando fai clic su un altro elemento possono complicare il mantenimento dell'accessibilità.

Gestione di un oggetto FileList

Poiché la maggior parte dei metodi sopra indicati produce un FileList, devo spiegare di cosa si tratta.

Un FileList è simile a un Array. Ha chiavi numeriche e una proprietà length, ma non è effettivamente un array. Non sono disponibili metodi di array, come forEach() o pop(), e non è iterabile. Naturalmente, puoi ottenere un array reale utilizzando Array.from(fileList).

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

<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 selezionate/incollate/rilasciate contemporaneamente.

Una volta ottenuto l'accesso al file, puoi utilizzarlo come preferisci. Ad esempio, puoi:

  • Disegnalo in un elemento <canvas> in modo da poterlo manipolare
  • Scaricalo sul dispositivo dell'utente
  • Caricalo su un server con fetch()

Accedere alla fotocamera in modo interattivo

Ora che hai acquisito le nozioni di base, è il momento di migliorare progressivamente.

I browser moderni possono accedere direttamente alle fotocamere, consentendoti di 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(). L'utente verrà quindi invitato a fornire l'accesso ai microfoni e alle videocamere collegati.

Il supporto di getUserMedia() è abbastanza buono, ma non è ancora disponibile ovunque. In particolare, non è disponibile in Safari 10 o versioni precedenti, che al momento della stesura di questo articolo è ancora la versione stabile più recente. Tuttavia, Apple ha annunciato che sarà disponibile in Safari 11.

Tuttavia, è molto facile rilevare l'assistenza.

const supported = 'mediaDevices' in navigator;

Quando chiami getUserMedia(), devi passare un oggetto che descriva il tipo di contenuti multimediali che vuoi. Queste scelte sono chiamate vincoli. Esistono diversi possibili vincoli, che riguardano ad esempio se preferisci una fotocamera anteriore o posteriore, se vuoi l'audio e la risoluzione che preferisci per lo stream.

Tuttavia, per ottenere i dati dalla videocamera, hai bisogno di un solo vincolo, ovvero video: true.

In caso di esito positivo, l'API restituirà un MediaStream contenente i dati della fotocamera, che potrai poi collegare a un elemento <video> e riprodurre per mostrare un'anteprima in tempo reale oppure collegare a un <canvas> per ottenere uno screenshot.

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

  const constraints = {
    video: true,
  };

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

Da solo, non è molto utile. Puoi solo acquisire i dati video e riprodurli. Se vuoi ottenere un'immagine, devi fare un po' di lavoro in più.

Acquisisci uno snapshot

La soluzione migliore 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 devi ricorrere a un po' di hacking per acquisire uno snapshot dalla fotocamera dell'utente.

La procedura è la seguente:

  1. Crea un oggetto canvas che conterrà l'inquadratura della videocamera
  2. Accedere allo stream della videocamera
  3. Allegarlo a un elemento video
  4. Quando vuoi acquisire un frame preciso, aggiungi i dati dell'elemento video a un oggetto canvas utilizzando drawImage().
<video id="player" controls playsinline 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 memorizzati i dati della videocamera nel canvas, puoi fare molte cose. Potresti:

  • Caricalo direttamente sul server
  • Memorizzarli localmente
  • Applicare effetti divertenti all'immagine

Suggerimenti

Interrompi lo streaming dalla videocamera quando non è necessario

È buona prassi smettere di utilizzare la videocamera quando non ne hai più bisogno. In questo modo, non solo risparmierai batteria e potenza di elaborazione, ma darai anche agli utenti la certezza che la tua app è affidabile.

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

<video id="player" controls playsinline 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>

Chiedere l'autorizzazione per utilizzare la fotocamera in modo responsabile

Se l'utente non ha mai concesso al tuo sito l'accesso alla videocamera, nel momento in cui chiami getUserMedia() il browser chiederà all'utente di concedere al tuo sito l'autorizzazione per la videocamera.

Gli utenti detestano che venga chiesto loro di accedere a dispositivi potenti sulla propria macchina e spesso bloccano la richiesta o la ignorano se non comprendendone il contesto per cui è stata creata. È buona prassi chiedere di accedere alla videocamera solo quando è necessario. Una volta che l'utente ha concesso l'accesso, non gli verrà chiesto di nuovo. Tuttavia, se l'utente rifiuta l'accesso, non potrai ottenerlo di nuovo, a meno che non modifichi manualmente le impostazioni dell'autorizzazione della fotocamera.

Compatibilità

Ulteriori informazioni sull'implementazione dei browser desktop e mobile:

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

Feedback