La plupart des navigateurs peuvent accéder à l'appareil photo 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 possèdent même pas de caméra. Comment créer une expérience qui utilise une image générée par l'utilisateur qui fonctionne partout ?
Commencer simplement et progressivement
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. Cette méthode fonctionne partout, car elle permet uniquement d'afficher l'image. 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. 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 en tant que multi-origine. Le seul moyen pratique consiste à exécuter un serveur proxy.
Saisie 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" />
Si vous ajoutez l'attribut capture
sans valeur, le navigateur décide quelle caméra utiliser, tandis que les valeurs "user"
et "environment"
indiquent au navigateur de privilégier les caméras 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 de signaler à l'utilisateur ce qui se passe lorsqu'il supprime le fichier à l'aide de la propriété dropEffect
.
Le glisser-déposer existe depuis longtemps et est 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 partie délicate de 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 de manière fluide 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 dois m'en dire un peu plus.
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
, sauf qu'ils disposent de propriétés name
et lastModified
supplémentaires en lecture seule.
<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>
afin de pouvoir le manipuler - Télécharger l'application sur l'appareil de l'utilisateur
- Importez-le sur un serveur avec
fetch()
.
Accéder à la caméra de manière interactive
Maintenant que vous avez assimilé vos bases, il est temps de vous 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 appelée getUserMedia()
dans la spécification WebRTC. L'utilisateur sera alors invité à accéder à ses micros et à ses 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, la prise en charge de ces derniers est très simple.
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>
En soi, ce n'est pas très utile. Il vous suffit 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 cadre de la vidéo sur un canevas.
Contrairement à l'API Web Audio, il n'existe pas d'API de traitement par flux dédiée à la vidéo sur le Web. Vous devez donc avoir recours à un peu de hacker pour prendre un instantané avec la caméra de l'utilisateur.
Le processus est le suivant :
- Créer un objet de canevas qui contiendra le cadre de l'appareil photo
- Accéder au flux de la caméra
- l'associer à 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 que les données de l'appareil photo sont stockées dans le canevas, vous pouvez les utiliser pour de nombreuses tâches. Vous pouvez :
- l'importer directement sur le serveur ;
- Stocker les données localement
- Appliquer des effets originaux à l'image
Conseils
Arrêter la diffusion du flux de la caméra lorsqu'elle n'est pas nécessaire
Nous vous recommandons d'arrêter d'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 empêcher l'accès à la caméra, il vous suffit d'appeler stop()
sur chaque piste vidéo du 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 la caméra de manière responsable
Si l'utilisateur n'a pas encore autorisé votre site à accéder à l'appareil photo,
l'instant où vous appelez getUserMedia()
, le navigateur invitera l'utilisateur
à autoriser votre site à accéder à l'appareil photo.
Les utilisateurs détestent être invités à accéder à des appareils puissants sur leur machine. Ils bloquent souvent la requête ou l'ignorent s'ils ne comprennent pas le contexte dans lequel l'invite 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, il n'est plus interrogé. 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é
En savoir plus sur l'implémentation dans les navigateurs mobiles et de bureau :
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.