La maggior parte dei browser può accedere alla videocamera 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 utilizzalo. Solo per mostrare l'immagine, funziona ovunque. Crea un elemento img
, imposta il valore src
e il gioco è fatto.
Tuttavia, se vuoi manipolare l'immagine in qualsiasi modo, le cose sono un po' più complicate. CORS ti impedisce di accedere ai pixel effettivi a meno che il server non imposti le intestazioni appropriate e segnali l'immagine come multiorigine; l'unico modo pratico è eseguire un server proxy.
Input file
Puoi anche utilizzare un semplice elemento di input dei 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 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.
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.
Se vuoi, 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" />
Se aggiungi l'attributo capture
senza un valore, è il browser a decidere quale videocamera utilizzare, mentre
i valori "user"
e "environment"
indicano al browser di preferire
la fotocamera anteriore e posteriore.
L'attributo capture
funziona su Android e iOS, ma viene ignorato su computer. Tieni presente, tuttavia, che su Android l'utente non avrà più la possibilità di scegliere un'immagine esistente. L'app di sistema della fotocamera verrà avviata direttamente.
Trascina
Se aggiungi già la possibilità di caricare un file, esistono un paio di semplici modi per migliorare 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 fare in modo che funzioni correttamente se non vuoi che l'utente sia in grado di 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 è in realtà un array. Non sono disponibili metodi di array, come forEach()
o pop()
, e non è iterabile.
Naturalmente, puoi ottenere un vero array 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 può anche gestire più immagini selezionate/incollate/rilasciate contemporaneamente.
Una volta ottenuto l'accesso al file, puoi eseguire qualsiasi operazione tu voglia. 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 dovrà quindi 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
.
Se l'operazione ha esito positivo, l'API restituirà un MediaStream
che contiene dati
della fotocamera e potrai quindi collegarlo a un elemento <video>
e riprodurlo
per mostrare un'anteprima in tempo reale oppure allegarlo a un <canvas>
per ottenere
un'istantanea.
<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>
Di per sé, 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 frame dal video a una tela.
A differenza dell'API Web Audio, non esiste un'API di elaborazione dello streaming dedicata per i video sul web, quindi è necessario ricorrere a una piccola attività di hacker per acquisire un'istantanea dalla videocamera dell'utente.
La procedura è la seguente:
- Crea un oggetto canvas che conterrà l'inquadratura della videocamera
- Accedere allo stream della videocamera
- Collegalo a un elemento video
- 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>
Dopo aver archiviato i dati della fotocamera nella tela, puoi usarli per svolgere molte attività. Potresti:
- Carica 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. 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 videocamera in modo responsabile
Se l'utente non ha mai concesso al tuo sito l'accesso alla videocamera,
al momento della chiamata a 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 concesso l'accesso, non gli verrà più chiesto di farlo. 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.