La mayoría de los navegadores pueden acceder a la cámara del usuario.
Muchos navegadores ahora pueden acceder a la entrada de audio y video del usuario. Sin embargo, según el navegador, puede ser una experiencia dinámica y intercalada completa, o bien puede delegarse 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 use una imagen generada por el usuario que funcione bien en todas partes?
Comienza de forma simple y progresiva
Si quieres mejorar tu experiencia de forma progresiva, debes comenzar con algo que funcione en todas partes. Lo más sencillo es pedirle al usuario un archivo grabado previamente.
Cómo solicitar una URL
Esta es la opción más compatible, pero menos satisfactoria. Pídele al usuario que te proporcione una URL y, luego, úsala. Para mostrar solo la imagen, esto funciona en todas partes. Crea un elemento img
, establece el src
y listo.
Sin embargo, si quieres manipular la imagen de alguna manera, las cosas se complican un poco. CORS te impide acceder a los píxeles reales, a menos que el servidor configure los encabezados adecuados y marques la imagen como de origen cruzado. La única forma práctica de evitar esto es ejecutar un servidor proxy.
Entrada de archivos
También puedes usar un elemento de entrada de archivo simple, incluido un filtro accept
que indique que solo quieres archivos de imagen.
<input type="file" accept="image/*" />
Este método funciona en todas las plataformas. En computadoras de escritorio, se le solicitará al usuario que suba un archivo de imagen desde el sistema de archivos. En Chrome y Safari para iOS y Android, este método le 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 si se escucha 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 hablaré más adelante.
De manera opcional, también puedes agregar el atributo capture
al elemento, que le 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" />
Agregar el atributo capture
sin un valor permite que el navegador decida qué cámara usar, mientras que los valores "user"
y "environment"
le indican al navegador que prefiera las cámaras frontal y posterior, respectivamente.
El atributo capture
funciona en iOS y Android, pero se ignora en computadoras. Sin embargo, ten en cuenta que, en Android, esto significa que el usuario ya no tendrá la opción de elegir una foto 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, hay algunas formas sencillas de enriquecer la experiencia del usuario.
La primera es agregar un destino de caída a tu página que le permita al usuario arrastrar un archivo desde la computadora de escritorio o desde 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 suelte el archivo con la propiedad dropEffect
.
El método de arrastrar y soltar existe desde hace mucho tiempo y es compatible con los principales navegadores.
Cómo 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
).
La parte complicada de la API del portapapeles es que, para obtener compatibilidad total con varios navegadores, el elemento de destino debe ser seleccionable y editable. Tanto <textarea>
como <input type="text">
cumplen con los requisitos aquí, al igual que los elementos con el atributo contenteditable
. Pero, obviamente, 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. 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
Dado que la mayoría de los métodos anteriores producen un FileList
, debo hablar un poco sobre qué es.
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
. Son exactamente iguales a los objetos Blob
,
excepto que tienen propiedades adicionales de solo lectura name
y lastModified
.
<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 MIME de imagen, pero también podría controlar varias imágenes que se seleccionan, pegan o dejan caer 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. - Descargarlo en el dispositivo del usuario
- Sube el archivo a un servidor con
fetch()
Accede a la cámara de forma interactiva
Ahora que ya conoces los aspectos básicos, es hora de mejorar de forma progresiva.
Los navegadores modernos pueden obtener acceso directo a las cámaras, lo que te permite crear experiencias que están completamente integradas en 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 con una API en la especificación de WebRTC llamada getUserMedia()
. Se le solicitará 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 o versiones anteriores, que, en el momento de escribir este artículo, sigue siendo la versión estable más reciente.
Sin embargo, Apple anunció que estará disponible en Safari 11.
Sin embargo, es muy fácil detectar la compatibilidad.
const supported = 'mediaDevices' in navigator;
Cuando llames a getUserMedia()
, debes pasar un objeto que describa el tipo de contenido multimedia que deseas. Estas opciones se denominan restricciones. Existen varias restricciones posibles, como si prefieres una cámara frontal o posterior, si quieres audio y la resolución que prefieres 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, puedes adjuntarlo a un elemento <video>
y reproducirlo para mostrar una vista previa en tiempo real, o bien adjuntarlo a un <canvas>
para obtener una instantánea.
<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>
Por sí solo, esto no es muy útil. Todo lo que puedes hacer es tomar los datos de video y reproducirlos. Si quieres obtener una imagen, debes hacer un poco más de trabajo.
Cómo tomar una instantánea
La mejor opción admitida para obtener una imagen es dibujar un fotograma del video en un lienzo.
A diferencia de la API de Web Audio, no hay una API de procesamiento de transmisiones dedicada para videos en la Web, por lo que debes recurrir a un poco de hackeo para capturar una instantánea de la cámara del usuario.
El proceso es el siguiente:
- Crea un objeto lienzo que contenga el marco de la cámara.
- Cómo obtener acceso a la transmisión de la cámara
- Adjúntalo a un elemento de video
- Cuando quieras capturar un fotograma preciso, agrega los datos del elemento de video a un objeto de lienzo con
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 vez que tengas datos de la cámara almacenados en el lienzo, podrás hacer muchas cosas con ellos. Intenta realizar lo siguiente:
- Subir el archivo directamente al servidor
- Almacenarlo de forma local
- Aplica efectos divertidos a la imagen
Sugerencias
Detén la transmisión desde la cámara cuando no sea necesario.
Se recomienda dejar de usar la cámara cuando ya no la necesites. Esto no solo ahorrará batería y potencia de procesamiento, sino que también les dará a los usuarios confianza en tu aplicación.
Para detener el acceso a la cámara, simplemente llama a stop()
en cada pista de video para la transmisión que muestra 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>
Solicita permiso para usar la cámara con responsabilidad
Si el usuario no le otorgó acceso a la cámara a tu sitio anteriormente, en el momento en que llames a getUserMedia()
, el navegador le pedirá al usuario que le otorgue permiso a tu sitio para usar la cámara.
A los usuarios no les gusta que se les solicite acceso a dispositivos potentes en su máquina, por lo que, a menudo, bloquean la solicitud o la ignoran si no comprenden el contexto para el que se creó la solicitud. Se recomienda solicitar acceso a la cámara solo cuando sea necesario. Una vez que el usuario otorgue el acceso, no se le volverá a preguntar. Sin embargo, si el usuario rechaza el acceso, no podrás volver a obtenerlo, a menos que cambie manualmente la configuración de permisos de la cámara.
Compatibilidad
Más información sobre la implementación de navegadores para dispositivos móviles y computadoras de escritorio:
También recomendamos usar el corrector de compatibilidad adapter.js para proteger las apps de los cambios en las especificaciones de WebRTC y las diferencias de prefijos.