La mayoría de los navegadores pueden acceder a la cámara del usuario.
Ahora muchos navegadores tienen la capacidad de acceder a la entrada de audio y video del usuario. Sin embargo, según el navegador, es posible que sea una experiencia en línea y dinámica completa, o se puede delegar a otra app en el dispositivo del usuario. Además, no todos los dispositivos tienen cámara. Entonces, ¿cómo puedes crear una experiencia que utilice una imagen generada por el usuario que funcione bien en todas partes?
Comenzar de forma simple y progresiva
Si deseas mejorar tu experiencia de forma progresiva, debes comenzar con algo que funcione en todas partes. Lo más fácil es simplemente pedirle al usuario un archivo grabado con anterioridad.
Solicitar una URL
Esta es la opción más compatible, pero la menos satisfactoria. Haz que el usuario te proporcione una URL y, luego, úsala. Para mostrar la imagen, funciona en todas partes. Crea un elemento img
, establece src
, y listo.
Sin embargo, si quieres manipular la imagen de cualquier manera, el proceso es un poco más complicado. CORS evita que accedas a los píxeles reales, a menos que el servidor establezca los encabezados adecuados y marcas la imagen como origen cruzado. La única manera práctica de evitar eso es ejecutar un servidor proxy.
Entrada del archivo
También puedes usar un elemento simple de entrada de archivo, incluido un filtro accept
que indique que solo deseas archivos de imagen.
<input type="file" accept="image/*" />
Este método funciona en todas las plataformas. En una computadora de escritorio, se le pedirá al usuario que suba un archivo de imagen desde el sistema de archivos. En Chrome y Safari en iOS y Android, este método permitirá al usuario elegir qué app usar para capturar la imagen, incluida la opción de tomar una foto directamente con la cámara o elegir un archivo de imagen existente.
Luego, los datos se pueden adjuntar a un <form>
o manipular con JavaScript. Para ello, se detecta un evento onchange
en el elemento de entrada y, luego, se lee la propiedad files
del 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 propiedad files
es un objeto FileList
, del que hablaremos más adelante.
De manera opcional, también puedes agregar el atributo capture
al elemento, que indica al navegador que prefieres obtener una imagen de la cámara.
<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />
Si agregas el atributo capture
sin un valor, el navegador decidirá qué cámara usar, mientras que los valores "user"
y "environment"
le indicarán al navegador que prefiera las cámaras frontal y posterior, respectivamente.
El atributo capture
funciona en iOS y Android, pero se ignora en computadoras de escritorio. Sin embargo, ten en cuenta que, en Android, esto significa que el usuario ya no tendrá la opción de elegir una imagen existente. En su lugar, se iniciará directamente la app de la cámara del sistema.
Arrastrar y soltar
Si ya agregas la capacidad de subir un archivo, existen varias formas sencillas de mejorar un poco la experiencia del usuario.
La primera es agregar un destino para soltar a la página que permita al usuario arrastrar un archivo desde el escritorio o alguna otra aplicación.
<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>
Al igual que con la entrada de archivo, puedes obtener un objeto FileList
de la propiedad dataTransfer.files
del evento drop
.
El controlador de eventos dragover
te permite indicarle al usuario lo que sucederá cuando deje el archivo
mediante la
propiedad dropEffect
.
La función de arrastrar y soltar existe desde hace mucho tiempo y es compatible con los principales navegadores.
Pegar desde el portapapeles
La última forma de obtener un archivo de imagen existente es desde el portapapeles. El código para esto es muy simple, pero la experiencia del usuario es un poco más difícil de hacer bien.
<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
es otro objeto FileList
más).
La parte complicada de la API del portapapeles es que, para la compatibilidad total entre navegadores, el elemento de destino debe poder seleccionarse y editarse. Tanto <textarea>
como <input type="text">
se ajustan a la cuenta aquí, al igual que los elementos con el atributo contenteditable
. Pero también están diseñados
para editar texto.
Puede ser difícil hacer que esto funcione sin problemas si no quieres que el usuario pueda ingresar texto. Algunos trucos, como tener una entrada oculta que se selecciona cuando haces clic en algún otro elemento, pueden dificultar el mantenimiento de la accesibilidad.
Controla un objeto FileList
Como la mayoría de los métodos anteriores producen un FileList
, debo explicar un poco de qué se trata.
Un FileList
es similar a un Array
. Tiene claves numéricas y una propiedad length
, pero en realidad no es un array. No hay métodos de array, como forEach()
o pop()
, y no es iterable.
Por supuesto, puedes obtener un Array real con Array.from(fileList)
.
Las entradas de FileList
son objetos File
. Estos son exactamente los mismos que los objetos Blob
, excepto que tienen propiedades name
y lastModified
adicionales de solo lectura.
<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>
En este ejemplo, se encuentra el primer archivo que tiene un tipo de MIME de imagen, pero también podría controlar que se seleccionen, se peguen o se suelten varias imágenes a la vez.
Una vez que tengas acceso al archivo, podrás hacer lo que quieras con él. Por ejemplo, puedes hacer lo siguiente:
- Dibújalo en un elemento
<canvas>
para que puedas manipularlo. - Descárgalo en el dispositivo del usuario
- Subirlo a un servidor con
fetch()
Cómo acceder a la cámara de forma interactiva
Ahora que has cubierto tus bases, es hora de mejorar progresivamente.
Los navegadores modernos pueden obtener acceso directo a las cámaras, lo que te permite crear experiencias que están completamente integradas con la página web, de modo que el usuario nunca tenga que salir del navegador.
Adquirir acceso a la cámara
Puedes acceder directamente a una cámara y un micrófono mediante una API en la especificación WebRTC llamada getUserMedia()
. Se le pedirá al usuario acceso a sus micrófonos y cámaras conectados.
La compatibilidad con getUserMedia()
es bastante buena, pero aún no está disponible en todas partes. En particular, no está disponible en Safari 10 ni versiones anteriores, que, al momento de escribir, sigue siendo la versión estable más reciente.
Sin embargo, Apple anunció que estará disponible en Safari 11.
Sin embargo, la detección de este servicio es muy simple.
const supported = 'mediaDevices' in navigator;
Cuando llamas a getUserMedia()
, debes pasar un objeto que describa el tipo de contenido multimedia que deseas. Estas opciones se denominan restricciones. Existen varias restricciones posibles, que abarcan aspectos como si prefieres una cámara frontal o posterior, si quieres audio y la resolución preferida para la transmisión.
Sin embargo, para obtener datos de la cámara, solo necesitas una restricción, que es video: true
.
Si se realiza correctamente, la API mostrará un MediaStream
que contiene datos de la cámara y, luego, podrás adjuntarlo a un elemento <video>
y reproducirlo para que se muestre una vista previa en tiempo real, o bien adjuntarlo a un <canvas>
para obtener una instantánea.
<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>
Por sí mismo, no es tan útil. Todo lo que puedes hacer es tomar los datos del video y reproducirlos. Si quieres obtener una imagen, debes hacer un poco de trabajo adicional.
Toma una instantánea
La mejor opción para obtener una imagen es dibujar un marco del video hasta un lienzo.
A diferencia de la API de Web Audio, no hay una API de procesamiento de transmisión exclusiva para videos en la Web, por lo que debes recurrir a un pequeño uso de hackeo para capturar una instantánea con la cámara del usuario.
El proceso es el siguiente:
- Crea un objeto de lienzo que sostenga el marco de la cámara
- Obtén acceso a la transmisión de la cámara
- Adjúntalo a un elemento de video
- Cuando quieras capturar un marco preciso, agrega los datos del elemento de video a un objeto de lienzo mediante
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 vez que tienes los datos de la cámara almacenados en el lienzo puedes hacer muchas cosas con ellos. Intenta hacer lo siguiente:
- Cargarlo directamente en el servidor
- Almacenarlos localmente
- Aplicar efectos llamativos a la imagen
Sugerencias
Deja de transmitir desde la cámara cuando no sea necesario
Te recomendamos que dejes de usar la cámara cuando ya no la necesites. Esto no solo ahorra batería y potencia de procesamiento, sino que también les brinda a los usuarios confianza en tu aplicación.
Para detener el acceso a la cámara, simplemente puedes llamar a stop()
en cada pista de video de la transmisión que muestra 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>
Solicitar permiso para usar la cámara de manera responsable
Si el usuario no le otorgó a tu sitio acceso a la cámara anteriormente, en el momento en que llames a getUserMedia()
, el navegador le pedirá al sitio que le otorgue permiso a la cámara.
A los usuarios no les gusta que se les solicite acceso a dispositivos potentes en su máquina y suelen bloquearla, o la ignoran si no comprenden el contexto para el cual se creó la solicitud. Te recomendamos que solo solicites acceso a la cámara la primera vez que se necesite. Una vez que el usuario haya otorgado acceso, no se le volverá a preguntar. Sin embargo, si el usuario rechaza el acceso, no podrás volver a obtener acceso, a menos que cambie de forma manual la configuración del permiso de la cámara.
Compatibilidad
Más información sobre la implementación de navegadores para dispositivos móviles y de escritorio:
También recomendamos usar la corrección de compatibilidad adapter.js para proteger las apps de los cambios de especificaciones de WebRTC y las diferencias de prefijos.