A maioria dos navegadores pode acessar a câmera do usuário.
Muitos navegadores agora têm a capacidade de 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 pode ser delegada a outro app 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 maneira mais fácil é pedir ao usuário um arquivo pré-gravado.
Solicitar um URL
Essa é a opção com melhor suporte, mas menos satisfatória. Peça ao usuário um URL e use-o. 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ê acesse os 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. No computador, o usuário será solicitado a fazer upload de um arquivo de imagem do sistema de arquivos. No Chrome e no Safari para iOS e Android, esse método permite que o usuário escolha 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.
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
, que vou 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 poderá mais escolher uma imagem
existente. Em vez disso, 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 à página para permitir que o usuário arraste um arquivo do computador 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 de arquivo, é possível acessar um objeto FileList
da propriedade dataTransfer.files
do
evento drop
.
O manipulador de eventos dragover
permite sinalizar ao usuário o que vai acontecer quando ele soltar 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 oferecer suporte total a vários 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 possa 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
, preciso falar um pouco sobre o que ele é.
Um FileList
é semelhante a um Array
. Ele tem chaves numéricas e uma propriedade length
, mas não é
realmente uma matriz. Não há métodos de matriz, como forEach()
ou pop()
, e ele não é iterável.
É claro que você pode receber uma matriz real usando Array.from(fileList)
.
As entradas da FileList
são objetos File
. Eles são exatamente iguais aos objetos Blob
,
exceto por terem propriedades name
e lastModified
somente leitura adicionais.
<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>
Esse exemplo encontra o primeiro arquivo que tem um tipo MIME de imagem, mas também pode processar várias imagens selecionadas/coladas/retiradas de uma só vez.
Depois de ter acesso ao arquivo, você pode fazer o que quiser com ele. Por exemplo, você pode:
- Desenhe-o em um elemento
<canvas>
para poder manipulá-lo. - Fazer o download no 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 precise sair do navegador.
Adquirir acesso à câmera
É possível acessar diretamente uma câmera e um microfone usando uma API na especificação
do WebRTC chamada getUserMedia()
. Isso vai solicitar ao usuário o
acesso aos microfones e câmeras conectados.
O suporte para getUserMedia()
é muito bom, mas ainda não está disponível 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()
, é necessário 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, que é video: true
.
Se bem-sucedida, a API vai retornar um MediaStream
que contém dados da
câmera. Você pode anexá-lo a um elemento <video>
e reproduzi-lo
para mostrar uma visualização em tempo real ou a um <canvas>
para receber um
instantâneo.
<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 o que você pode fazer é pegar os dados do vídeo e reproduzi-los. Se você quiser uma imagem, terá que fazer um pouco mais de trabalho.
Criar um snapshot
A melhor opção para conseguir 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ídeo na Web. Portanto, é necessário recorrer a um pouco de hackery para capturar uma imagem da câmera do usuário.
O processo é o seguinte:
- Crie um objeto de tela que vai conter o frame da câmera
- Ter acesso ao stream da câmera
- Anexar a um elemento de vídeo
- 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 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>
Depois de armazenar os dados da câmera na tela, você pode fazer muitas coisas com eles. Você pode:
- Faça o upload diretamente no servidor
- Armazenar localmente
- Aplicar efeitos divertidos à imagem
Dicas
Interromper a transmissão da câmera quando não for necessário
É recomendável parar de usar a câmera quando não for mais necessário. Isso não apenas economiza bateria e capacidade de processamento, mas também dá confiança aos usuários no seu aplicativo.
Para interromper o acesso à câmera, basta chamar stop()
em cada faixa de vídeo
para o stream 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 de forma responsável
Se o usuário não tiver concedido acesso à câmera ao seu site,
assim que você chamar getUserMedia()
, o navegador vai pedir ao usuário para
conceder permissão ao site para usar a câmera.
Os usuários detestam receber solicitações de acesso a dispositivos poderosos na máquina e, com frequência, bloqueiam a solicitação ou a ignoram se não entenderem o contexto em que a solicitação foi criada. É recomendável solicitar o acesso à câmera somente quando for necessário. Depois que o usuário conceder o acesso, ele não será solicitado novamente. No entanto, se o usuário rejeitar o acesso, não será possível ter acesso 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 shim adapter.js para proteger apps de mudanças de especificação e diferenças de prefixo do WebRTC.