Como capturar uma imagem do usuário

A maioria dos navegadores pode acessar a câmera do usuário.

Balanças de tapetes

Agora, muitos navegadores podem acessar a entrada de vídeo e áudio do usuário. No entanto, dependendo do navegador, pode ser uma experiência dinâmica e inline completa ou delegada a outro app no dispositivo do usuário. Além disso, nem todos os dispositivos têm câmera. Então, como você pode criar uma experiência que usa uma imagem gerada pelo usuário que funciona bem em qualquer lugar?

Comece de maneira simples e progressiva

Se você quiser melhorar sua experiência progressivamente, comece com algo que funcione em qualquer lugar. A coisa mais fácil a fazer é simplesmente pedir ao usuário um arquivo pré-gravado.

Pedir um URL

Essa é a opção com melhor suporte, mas menos satisfatória. Faça com que o usuário forneça um URL e use-o. Para mostrar a imagem, isso funciona em qualquer lugar. Crie um elemento img, defina o src e pronto.

No entanto, se você quiser manipular a imagem de alguma forma, as coisas são um pouco mais complicadas. O CORS impede que você acesse os pixels reais, a menos que o servidor defina os cabeçalhos apropriados e você marque a imagem como crossorigin. A única maneira prática de fazer isso é executar um servidor proxy.

Entrada de arquivo

Você também pode usar um elemento de entrada de arquivo simples, incluindo um filtro accept que indique que você quer apenas arquivos de imagem.

<input type="file" accept="image/*" />

Esse método funciona em todas as plataformas. Em computadores, será solicitado que o usuário envie um arquivo de imagem do sistema de arquivos. No Chrome e no Safari no iOS e no Android, esse método oferece ao usuário a escolha de qual app usar para capturar a imagem, incluindo a opção de tirar uma foto diretamente com a câmera ou escolher um arquivo de imagem existente.

Um menu do Android com duas opções: capturar imagem e arquivos Um menu do iOS com três opções: tirar foto, biblioteca de fotos, iCloud

Os dados podem ser anexados a um <form> ou manipulados com JavaScript detectando um evento onchange no elemento de entrada e lendo a propriedade files do 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>

A propriedade files é um objeto FileList. Falaremos mais sobre isso depois.

Também é possível adicionar o atributo capture ao elemento, que indica ao navegador que você prefere receber uma imagem da câmera.

<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />

Adicionar o atributo capture sem um valor permite que o navegador decida qual câmera usar, enquanto os valores "user" e "environment" orientam o navegador a preferir as câmeras frontal e traseira, respectivamente.

O atributo capture funciona no Android e iOS, mas é ignorado em computadores. No entanto, no Android, isso significa que o usuário não terá mais a opção de escolher uma imagem existente. O app de câmera do sistema será iniciado diretamente.

Arrastar e soltar

Se você já está adicionando a capacidade de fazer upload de um arquivo, há algumas maneiras fáceis de melhorar a experiência do usuário.

A primeira é adicionar um destino de soltar à sua página que permita ao usuário arrastar um arquivo da área de trabalho ou de outro aplicativo.

<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>

Assim como na entrada do arquivo, é possível acessar um objeto FileList da propriedade dataTransfer.files do evento drop.

O manipulador de eventos dragover permite indicar ao usuário o que acontecerá quando ele descartar o arquivo usando a propriedade dropEffect.

O recurso de arrastar e soltar existe há muito tempo e é compatível com os principais navegadores.

Colar da área de transferência

A última forma de conseguir um arquivo de imagem é usando a área de transferência. O código para isso é muito simples, mas a experiência do usuário é um pouco mais difícil de acertar.

<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 é mais um objeto FileList.)

A parte complicada com a API de área de transferência é que, para oferecer suporte total a vários navegadores, o elemento de destino precisa ser selecionável e editável. Tanto <textarea> quanto <input type="text"> se encaixam nessa questão, assim como os elementos com o atributo contenteditable. Mas eles também são obviamente projetados para editar textos.

Pode ser difícil fazer isso funcionar sem problemas se você não quiser que o usuário possa inserir texto. Truques como usar uma entrada oculta que é selecionada quando você clica em algum outro elemento pode dificultar a manutenção da acessibilidade.

Manipulação de um objeto FileList

Como a maioria dos métodos acima produz uma FileList, vou falar um pouco sobre isso.

Uma FileList é semelhante a uma Array. Ela tem chaves numéricas e uma propriedade length, mas não é uma matriz. Não há métodos de matriz, como forEach() ou pop(), e ela não é iterável. É claro que é possível ter uma matriz real usando Array.from(fileList).

As entradas de FileList são objetos File. Eles são exatamente iguais aos objetos Blob, exceto pelo fato de terem mais propriedades somente leitura name e 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>

Este exemplo encontra o primeiro arquivo que tem um tipo MIME de imagem, mas também pode processar várias imagens que são selecionadas/coladas/descartadas de uma só vez.

Depois de ter acesso ao arquivo, você poderá fazer o que quiser com ele. Por exemplo, você pode:

  • Desenhe-o em um elemento <canvas> para que possa ser manipulado.
  • Fazer o download para o dispositivo do usuário
  • Fazer upload em um servidor com fetch()

Acessar a câmera de forma interativa

Agora que você cobriu suas bases, é hora de melhorar progressivamente!

Os navegadores modernos podem ter acesso direto às câmeras, permitindo que você crie experiências totalmente integradas à página da Web, para que o usuário nunca precise sair do navegador.

Ter acesso à câmera

É possível acessar diretamente uma câmera e um microfone usando uma API na especificação WebRTC chamada getUserMedia(). Isso vai solicitar que o usuário acesse as câmeras e os microfones conectados.

A compatibilidade com getUserMedia() é muito boa, mas ainda não está em todos os lugares. Em particular, ele não está disponível no Safari 10 ou versões anteriores, que ainda é a versão estável mais recente no momento. No entanto, a Apple anunciou que estará disponível no Safari 11.

No entanto, é muito simples detectar a compatibilidade.

const supported = 'mediaDevices' in navigator;

Ao chamar getUserMedia(), você precisa transmitir um objeto que descreva o tipo de mídia que você quer. Essas escolhas são chamadas de restrições. Há várias restrições possíveis, que abrangem coisas como se você prefere usar uma câmera frontal ou traseira, se quer áudio e sua resolução preferida para a transmissão.

Para extrair dados da câmera, no entanto, você precisa de apenas uma restrição: video: true.

Se for bem-sucedida, a API vai retornar um MediaStream que contém dados da câmera. Você poderá anexá-lo a um elemento <video> e reproduzi-lo para mostrar uma visualização em tempo real ou anexá-lo a um <canvas> para gerar um snapshot.

<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 si só, isso não é tão útil. Tudo que você pode fazer é pegar os dados do vídeo e reproduzi-lo. Se quiser obter uma imagem, você terá que fazer um pouco mais de trabalho.

Capturar um snapshot

A melhor opção disponível para gerar uma imagem é desenhar um frame do vídeo em uma tela.

Ao contrário da API Web Audio, não há uma API de processamento de stream dedicada para vídeos na Web. Portanto, você precisa recorrer a um pequeno ataque de hackers para capturar um instantâneo da câmera do usuário.

O processo é o seguinte:

  1. Crie um objeto de tela que segurará o frame da câmera
  2. Acessar o stream da câmera
  3. Anexar a um elemento de vídeo
  4. Quando você quiser capturar um frame preciso, adicione os dados do elemento de vídeo a um objeto de tela usando 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>

Depois de armazenar os dados da câmera no canvas, você pode fazer muitas coisas com eles. É possível:

  • Carregá-los diretamente no servidor
  • Armazenar localmente
  • Aplicar efeitos divertidos à imagem

Dicas

Interromper o streaming da câmera quando não for necessário

É recomendável parar de usar a câmera quando ela não for mais necessária. Isso não apenas economiza bateria e capacidade de processamento, mas também dá confiança aos usuários no aplicativo.

Para interromper o acesso à câmera, basta chamar stop() em cada faixa de vídeo do stream retornado por 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>

Peça permissão para usar a câmera com responsabilidade

Se o usuário ainda não tiver concedido ao site acesso à câmera, no momento em que você chamar getUserMedia(), o navegador vai solicitar que o usuário conceda permissão ao site.

Os usuários odeiam receber solicitações de acesso a dispositivos potentes na máquina e, com frequência, bloqueiam a solicitação ou a ignoram se não entendem o contexto para o qual a solicitação foi criada. É uma prática recomendada só pedir acesso à câmera na primeira vez que for necessário. Depois que o usuário concede acesso, ele não precisa fazer isso novamente. No entanto, se o usuário rejeitar o acesso, você não poderá acessá-lo novamente, a menos que ele mude manualmente as configurações de permissão da câmera.

Compatibilidade

Mais informações sobre a implementação de navegadores para dispositivos móveis e computadores:

Também recomendamos usar o paliativo adapter.js para proteger apps contra mudanças na especificação WebRTC e diferenças de prefixo.

Feedback