Captura una imagen del usuario

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.

Un menú de Android con dos opciones: capturar imágenes y archivos Un menú de iOS con tres opciones: Tomar foto, Biblioteca de fotos y iCloud

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:

  1. Crea un objeto de lienzo que sostenga el marco de la cámara
  2. Obtén acceso a la transmisión de la cámara
  3. Adjúntalo a un elemento de video
  4. 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.

Comentarios