La plupart des navigateurs peuvent accéder à la caméra de l'utilisateur.
De nombreux navigateurs peuvent désormais accéder aux entrées vidéo et audio de l'utilisateur. Toutefois, selon le navigateur, il peut s'agir d'une expérience entièrement dynamique et intégrée, ou d'une délégation à une autre application sur l'appareil de l'utilisateur. De plus, tous les appareils ne sont pas équipés d'une caméra. Comment créer une expérience qui utilise une image générée par l'utilisateur qui fonctionne partout ?
Commencez par des actions simples et progressives
Si vous souhaitez améliorer progressivement votre expérience, vous devez commencer par quelque chose qui fonctionne partout. La méthode la plus simple consiste à demander à l'utilisateur de fournir un fichier préenregistré.
Demander une URL
Il s'agit de l'option la plus acceptée, mais la moins satisfaisante. Demandez à l'utilisateur de vous fournir une URL, puis utilisez-la. Pour afficher uniquement l'image, cette méthode fonctionne partout. Créez un élément img
, définissez src
et le tour est joué.
Toutefois, si vous souhaitez manipuler l'image de quelque manière que ce soit, les choses sont un peu plus compliquées. Le CORS vous empêche d'accéder aux pixels réels, sauf si le serveur définit les en-têtes appropriés et que vous marquez l'image comme crossorigin. Le seul moyen pratique de contourner ce problème est d'exécuter un serveur proxy.
Entrée de fichier
Vous pouvez également utiliser un élément d'entrée de fichier simple, y compris un filtre accept
qui indique que vous ne souhaitez que des fichiers image.
<input type="file" accept="image/*" />
Cette méthode fonctionne sur toutes les plates-formes. Sur ordinateur, l'utilisateur est invité à importer un fichier image à partir du système de fichiers. Dans Chrome et Safari sur iOS et Android, cette méthode permet à l'utilisateur de choisir l'application à utiliser pour capturer l'image, y compris la possibilité de prendre une photo directement avec l'appareil photo ou de choisir un fichier image existant.
Les données peuvent ensuite être associées à un <form>
ou manipulées avec JavaScript en écoutant un événement onchange
sur l'élément de saisie, puis en lisant la propriété files
de l'événement 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 propriété files
est un objet FileList
, dont je parlerai plus en détail ultérieurement.
Vous pouvez également ajouter l'attribut capture
à l'élément, ce qui indique au navigateur que vous préférez obtenir une image de la caméra.
<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />
L'ajout de l'attribut capture
sans valeur permet au navigateur de choisir la caméra à utiliser, tandis que les valeurs "user"
et "environment"
indiquent au navigateur de privilégier la caméra avant et arrière, respectivement.
L'attribut capture
fonctionne sur Android et iOS, mais est ignoré sur ordinateur. Notez toutefois que sur Android, cela signifie que l'utilisateur n'aura plus la possibilité de choisir une image existante. L'application d'appareil photo du système sera démarrée directement.
Glisser-déposer
Si vous ajoutez déjà la possibilité d'importer un fichier, vous pouvez facilement améliorer l'expérience utilisateur.
La première consiste à ajouter une cible de dépôt à votre page, qui permet à l'utilisateur de faire glisser un fichier depuis le bureau ou une autre application.
<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>
Comme pour l'entrée de fichier, vous pouvez obtenir un objet FileList
à partir de la propriété dataTransfer.files
de l'événement drop
.
Le gestionnaire d'événements dragover
vous permet d'indiquer à l'utilisateur ce qui se passe lorsqu'il dépose le fichier à l'aide de la propriété dropEffect
.
Le glisser-déposer existe depuis longtemps et est bien pris en charge par les principaux navigateurs.
Coller depuis le presse-papiers
La dernière façon d'obtenir un fichier image existant est de le récupérer depuis le presse-papiers. Le code est très simple, mais l'expérience utilisateur est un peu plus difficile à obtenir.
<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
est encore un autre objet FileList
.)
La difficulté avec l'API du presse-papiers est que, pour une compatibilité complète entre les navigateurs, l'élément cible doit être à la fois sélectionnable et modifiable. <textarea>
et <input type="text">
conviennent ici, tout comme les éléments avec l'attribut contenteditable
. Mais ils sont également conçus pour modifier du texte.
Il peut être difficile de faire fonctionner ce processus correctement si vous ne souhaitez pas que l'utilisateur puisse saisir du texte. Des astuces telles qu'une entrée masquée qui est sélectionnée lorsque vous cliquez sur un autre élément peuvent rendre la maintenance de l'accessibilité plus difficile.
Gérer un objet FileList
Étant donné que la plupart des méthodes ci-dessus génèrent un FileList
, je vais vous en dire un peu plus sur ce qu'il s'agit.
Un FileList
est semblable à un Array
. Il comporte des clés numériques et une propriété length
, mais il ne s'agit pas d'un tableau. Il n'existe aucune méthode d'array, comme forEach()
ou pop()
, et il n'est pas itérable.
Bien sûr, vous pouvez obtenir un tableau réel à l'aide de Array.from(fileList)
.
Les entrées de FileList
sont des objets File
. Ils sont exactement identiques aux objets Blob
, à l'exception de leurs propriétés en lecture seule name
et lastModified
supplémentaires.
<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>
Cet exemple recherche le premier fichier dont le type MIME est une image, mais il peut également gérer plusieurs images sélectionnées/collées/déposées en même temps.
Une fois que vous avez accès au fichier, vous pouvez l'utiliser comme vous le souhaitez. Par exemple, vous pouvez :
- Dessinez-le dans un élément
<canvas>
pour pouvoir le manipuler. - Télécharger l'application sur l'appareil de l'utilisateur
- Importer le fichier sur un serveur avec
fetch()
Accéder à l'appareil photo de manière interactive
Maintenant que vous avez les bases, il est temps de les améliorer progressivement.
Les navigateurs modernes peuvent accéder directement aux appareils photo, ce qui vous permet de créer des expériences entièrement intégrées à la page Web. L'utilisateur n'a donc jamais besoin de quitter le navigateur.
Accéder à l'appareil photo
Vous pouvez accéder directement à une caméra et à un micro à l'aide d'une API de la spécification WebRTC appelée getUserMedia()
. L'utilisateur sera alors invité à autoriser l'accès à ses micros et caméras connectés.
La compatibilité avec getUserMedia()
est plutôt bonne, mais elle n'est pas encore disponible partout. En particulier, il n'est pas disponible dans Safari 10 ou version antérieure, qui est toujours la dernière version stable au moment de la rédaction de cet article.
Toutefois, Apple a annoncé qu'il sera disponible dans Safari 11.
Toutefois, il est très simple de détecter la prise en charge.
const supported = 'mediaDevices' in navigator;
Lorsque vous appelez getUserMedia()
, vous devez transmettre un objet qui décrit le type de contenu multimédia souhaité. Ces choix sont appelés "contraintes". Plusieurs contraintes sont possibles, par exemple si vous préférez une caméra avant ou arrière, si vous souhaitez de l'audio et la résolution de votre choix pour la diffusion.
Toutefois, pour obtenir des données de la caméra, vous n'avez besoin que d'une seule contrainte : video: true
.
Si l'opération aboutit, l'API renvoie un MediaStream
contenant les données de la caméra. Vous pouvez ensuite l'associer à un élément <video>
et le lire pour afficher un aperçu en temps réel, ou l'associer à un <canvas>
pour obtenir un instantané.
<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>
Cela n'est pas très utile en soi. Tout ce que vous pouvez faire est de récupérer les données vidéo et de les lire. Si vous souhaitez obtenir une image, vous devez effectuer un peu de travail supplémentaire.
Prendre un instantané
La meilleure option pour obtenir une image est de dessiner un frame de la vidéo sur un canevas.
Contrairement à l'API Web Audio, il n'existe pas d'API de traitement de flux dédiée pour la vidéo sur le Web. Vous devez donc recourir à un peu de piratage pour capturer un instantané de l'appareil photo de l'utilisateur.
Le processus est le suivant :
- Créez un objet canevas qui contiendra le frame de l'appareil photo.
- Accéder au flux de la caméra
- Joindre un élément vidéo
- Lorsque vous souhaitez capturer un frame précis, ajoutez les données de l'élément vidéo à un objet canvas à l'aide de
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>
Une fois les données de la caméra stockées dans le canevas, vous pouvez en faire beaucoup. Vous pouvez :
- Importer directement sur le serveur
- Stocker les données localement
- Appliquer des effets originaux à l'image
Conseils
Arrêter le streaming depuis la caméra lorsqu'il n'est pas nécessaire
Il est recommandé de ne plus utiliser la caméra lorsque vous n'en avez plus besoin. Cela permet non seulement d'économiser de la batterie et de la puissance de traitement, mais aussi de rassurer les utilisateurs sur votre application.
Pour arrêter l'accès à la caméra, vous pouvez simplement appeler stop()
sur chaque piste vidéo pour le flux renvoyé par 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>
Demander l'autorisation d'utiliser l'appareil photo de manière responsable
Si l'utilisateur n'a pas encore autorisé votre site à accéder à la caméra, le navigateur invite l'utilisateur à autoriser votre site à accéder à la caméra dès que vous appelez getUserMedia()
.
Les utilisateurs détestent être invités à accéder à des appareils puissants sur leur ordinateur. Ils bloquent souvent la requête ou l'ignorent s'ils ne comprennent pas le contexte dans lequel elle a été créée. Il est recommandé de ne demander l'accès à la caméra que lorsque cela est nécessaire. Une fois que l'utilisateur a accordé l'accès, cette étape n'est plus nécessaire. Toutefois, si l'utilisateur refuse l'accès, vous ne pouvez plus y accéder, sauf s'il modifie manuellement les paramètres d'autorisation de l'appareil photo.
Compatibilité
Pour en savoir plus sur l'implémentation dans les navigateurs mobiles et pour ordinateur :
Nous vous recommandons également d'utiliser le shim adapter.js pour protéger les applications contre les modifications de la spécification WebRTC et les différences de préfixe.