Como capturar uma imagem do usuário

A maioria dos navegadores pode ter acesso à câmera do usuário.

Atualmente, muitos navegadores têm o recurso de acessar inserções de dados de áudio e vídeo do usuário. No entanto, dependendo do navegador, essa pode ser uma experiência dinâmica e integrada ou delegada a outro aplicativo no dispositivo do usuário. Além disso, nem todos os dispositivos têm uma câmera. Como criar uma experiência que use uma imagem gerada pelo usuário que funcione bem em todos os lugares?

Comece de forma simples e progressiva

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

Peça um URL

Essa é a opção com melhor suporte, mas menos satisfatória. Peça ao usuário um URL e use ele. Para mostrar apenas 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 ficam um pouco mais complicadas. O CORS impede que você acceda aos pixels reais, a menos que o servidor defina os cabeçalhos apropriados e você marque a imagem como cross-origin. A única maneira prática de contornar isso é executar um servidor proxy.

Entrada de arquivo

Também é possível usar um elemento de entrada de arquivo simples, incluindo um filtro accept que indica que você só quer arquivos de imagem.

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

Esse método funciona em todas as plataformas. Em computadores, o usuário poderá fazer upload de um arquivo de imagem do sistema de arquivos. No Chrome e Safari para iOS e Android, esse método oferece ao usuário a opção 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 já 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 e iCloud

Em seguida, os dados podem ser anexados a um <form> ou manipulados com JavaScript pela detecção de um evento onchange no elemento de entrada e, em seguida, 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, que vamos abordar mais adiante.

Também é possível adicionar o atributo capture ao elemento, o 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" informam ao navegador que ele deve preferir as câmeras frontal e traseira, respectivamente.

O atributo capture funciona no Android e no 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 à página um destino de soltar 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 receber um objeto FileList da propriedade dataTransfer.files do evento drop.

O manipulador de eventos dragover permite que você sinalize 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 maneira de acessar um arquivo de imagem é pela á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 é outro objeto FileList.

A parte complicada da API da área de transferência é que, para compatibilidade total entre navegadores, o elemento de destino precisa ser selecionável e editável. <textarea> e <input type="text"> se encaixam aqui, assim como elementos com o atributo contenteditable. Mas elas também são projetadas para editar texto.

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

Como processar um objeto FileList

Como a maioria dos métodos acima produz um FileList, vou falar um pouco sobre o que ele é.

Um FileList é semelhante a um Array. Ele tem chaves numéricas e uma propriedade length, mas, na verdade, não é uma matriz. Não há métodos de matriz, como forEach() ou pop(), e ela não é iterável. É claro que você pode receber uma matriz real usando Array.from(fileList).

As entradas do FileList são objetos File. Eles são exatamente iguais aos objetos Blob, exceto pelo fato de terem outras 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 estão sendo selecionadas/coladas/soltadas de uma só vez.

Depois que você tiver acesso ao arquivo, pode fazer o que quiser com ele. Por exemplo, você pode:

  • Desenhe-o em um elemento <canvas> para poder manipulá-lo
  • Fazer o download para o dispositivo do usuário
  • Faça upload para um servidor com fetch()

Acessar a câmera de forma interativa

Agora que você já tem as 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 saia do navegador.

Obter acesso à câmera

É possível acessar diretamente uma câmera e um microfone usando uma API na especificação WebRTC chamada getUserMedia(). Assim, enviamos uma solicitação de acesso às câmeras e microfones ao usuário.

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

No entanto, é muito simples detectar o suporte.

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, cobrindo aspectos como se você prefere uma câmera frontal ou traseira, se quer áudio e qual é a resolução preferida para a transmissão.

No entanto, para receber dados da câmera, 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. É possível 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 playsinline 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 você quiser uma imagem, terá que fazer um pouco mais de trabalho.

Capturar um snapshot

A melhor opção para extrair 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 fluxo dedicada para vídeos na Web. Por isso, é necessário recorrer a um pouquinho do jeitinho brasileiro para capturar uma foto da câmera do usuário.

O processo é o seguinte:

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

Quando tiver os dados da câmera armazenados no "canvas", você vai poder fazer muitas coisas com eles. Você pode:

  • Faça o upload direto no servidor.
  • Armazenar localmente
  • Aplicar efeitos modernos à imagem

Dicas

Pare de transmitir da câmera quando não for necessário

É uma boa prática parar de usar a câmera quando ela não é mais necessária. Isso não só economiza bateria e capacidade de processamento, mas também faz com que os usuários confiem no seu aplicativo.

Para parar de acessar a câmera, basta chamar stop() em cada trilha de vídeo do fluxo retornado por 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>

Pedir permissão para usar a câmera com responsabilidade

Se o usuário ainda não tiver concedido acesso à câmera para o seu site, no instante em que você chamar getUserMedia(), o navegador vai pedir que o usuário autorize o seu site a acessar a câmera.

Os usuários odeiam receber solicitações de permissão de acesso a dispositivos importantes do seu aparelho e muitas vezes bloqueiam a solicitação ou a ignoram se não entendem por que a solicitação foi criada. A prática recomendada é pedir acesso à câmera apenas quando ela for necessária. Depois que o usuário conceder acesso, ele não receberá mais solicitações de permissão de acesso. No entanto, se o usuário não der a permissão, você não poderá acessar novamente, a menos que ele mude manualmente as configurações de permissão da câmera.

Compatibilidade

Saiba mais sobre a implementação em navegadores para dispositivos móveis e computadores:

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

Feedback