Normalmente, quando fazemos o upload de uma imagem nós precisamos redimensiona-lá para reduzir seu tamanho. Esse processo normalmente é feito do lado servidor, ou seja, enviamos a imagem na sua forma e tamanho original e então redimensionamos no servidor utilizando PHP, por exemplo.
Porém, em um aplicativo de galeria de fotos, por exemplo, o usuário normalmente baixa as fotos (em alta resolução) de sua câmera e quer em seguida enviar para o aplicativo. Supondo que ele deseja enviar 200 fotos, cada uma com 5 MB, seria necessário fazer o upload de 1 GB de dados para o servidor, para que então possamos reduzi-las em um tamanho viável.
Sendo assim, pretendo apresentar nesse artigo uma solução para este problema. Vamos ver como redimensionar a imagem antes de enviar ao servidor, utilizando Javascript. Para isso faremos um formulário para envio de várias imagens que serão salvas em uma pasta no servidor.
Confira o exemplo em funcionamento, clicando aqui.
O formulário
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cadastro de Foto</title>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="js/canvas-to-blob.min.js"></script>
<script src="js/resize.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h1>Cadastro de Foto</h1>
<form method="post" action="#" role="form">
<div class="progress">
<div id="progresso" class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div>
</div>
<div class="form-group row">
<div class="col-xs-12">
<input id="imagem" type="file" accept="image/*" multiple/>
</div>
</div>
</form>
</div>
</body>
</html>
Criamos um formulário com um input para envio das imagens. Utilizamos a propriedade accept para definir que só serão permitidos imagens e a propriedade multiple que permite o envio de várias imagens. Criamos também uma barra de progresso que será utilizada para indicar o progresso de envio, utilizando Bootstrap. Além do JQuery e Bootstrap, utilizaremos também a biblioteca resize.js que facilitará o redimensionamento.
O lado cliente
Primeiramente, vamos declarar as variáveis que serão utilizadas e definir os eventos.
// Iniciando biblioteca
var resize = new window.resize();
resize.init();
// Declarando variáveis
var imagens;
var imagem_atual;
// Quando carregado a página
$(function ($) {
// Quando selecionado as imagens
$('#imagem').on('change', function () {
enviar();
});
});
Iniciamos a biblioteca, criamos a variável imagens que será utilizada para armazenar as imagens e a variável imagem_atual que será utilizada para controlar o índice da imagem que está sendo enviada. Vinculamos um evento change no nosso input, ou seja, quando o usuário selecionar os arquivo esse evento será chamado, que por sua vez, chamará a função enviar().
Enviar
Vamos criar a função para iniciar o envio da imagens.
/*
Envia os arquivos selecionados
*/
function enviar()
{
// Verificando se o navegador tem suporte aos recursos para redimensionamento
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
alert('O navegador não suporta os recursos utilizados pelo aplicativo');
return;
}
// Alocando imagens selecionadas
imagens = $('#imagem')[0].files;
// Se selecionado pelo menos uma imagem
if (imagens.length > 0)
{
// Definindo progresso de carregamento
$('#progresso').attr('aria-valuenow', 0).css('width', '0%');
// Escondendo campo de imagem
$('#imagem').hide();
// Iniciando redimensionamento
imagem_atual = 0;
redimensionar();
}
}
Primeiramente, verificamos se o navegador do usuário possui os recursos necessários para manipulação de arquivos. Alocamos as imagens selecionadas na variável imagens. Verificamos se alguma imagem foi selecionada; caso haja, iniciamos a barra de progresso, escondemos o input, zeramos a variável de controle e chamamos a função redimensionar().
Redimensionar
Essa função é responsável por redimensionar, salvar e passar para próxima imagem recursivamente. Isso é necessário pois o processo de redimensionamento da imagem é assíncrono, ou seja, o resultado não é retornado imediatamente, portanto não podemos fazer um simples for percorrendo as imagens e redimensionando. Sendo assim, a variável global imagem_atual é quem controla o fluxo das imagens.
/*
Redimensiona uma imagem e passa para a próxima recursivamente
*/
function redimensionar()
{
// Se redimensionado todas as imagens
if (imagem_atual > imagens.length)
{
// Definindo progresso de finalizado
$('#progresso').html('Imagen(s) enviada(s) com sucesso');
// Limpando imagens
limpar();
// Exibindo campo de imagem
$('#imagem').show();
// Finalizando
return;
}
// Se não for um arquivo válido
if ((typeof imagens[imagem_atual] !== 'object') || (imagens[imagem_atual] == null))
{
// Passa para a próxima imagem
imagem_atual++;
redimensionar();
return;
}
// Redimensionando
resize.photo(imagens[imagem_atual], 800, 'dataURL', function (imagem) {
// Salvando imagem no servidor
$.post('ajax/salvar.php', {imagem: imagem}, function() {
// Definindo porcentagem
var porcentagem = (imagem_atual + 1) / imagens.length * 100;
// Atualizando barra de progresso
$('#progresso').text(Math.round(porcentagem) + '%').attr('aria-valuenow', porcentagem).css('width', porcentagem + '%');
// Aplica delay de 1 segundo
// Apenas para evitar sobrecarga de requisições
// e ficar visualmente melhor o progresso
setTimeout(function () {
// Passa para a próxima imagem
imagem_atual++;
redimensionar();
}, 1000);
});
});
}
- Linha 7 até 20: Essa função será chamada recursivamente, ou seja, por ela mesmo. Portanto precisamos ter um ponto final, que no caso é quando nossa variável de controle
imagem_atualfor maior que a quantidade de imagens. Caso isso aconteça, exibimos uma mensagem de sucesso e finalizamos a chamada da função; - Linha 23 até 29: Este teste é necessário, pois alguns navegadores incluem um valor nulo quando armazenamos a imagem do
input, portanto caso não seja uma imagem válida, passamos para a próxima imagem, ou seja, incrementamos a variável de controle, chamamos a função novamente e finalizamos essa chamada; - Linha 32: Chamamos o método de redimensionamento da biblioteca. Passamos como parâmetro a imagem (
imagens[imagem_atual]), a largura em pixel para qual a imagem será redimensionada, o tipo do retorno (dataURLoufile) e por fim ocallback, ou seja, a função que será chamada quando o processo finalizar; - Linha 35: Neste trecho a imagem já foi redimensionada e está armazenada na variável
imagem, portanto fazemos uma requisição AJAX ao servidor, passando a imagem via método POST; - Linha 38: Através de um cálculo simples, definimos a porcentagem que a imagem atual representa no total de imagens;
- Linha 41: Definimos a porcentagem atual da barra de progresso;
- Linha 46 até 50: Através da função
setTimeout(), definimos que o trecho de código adiante só será chamado após um segundo. Isso é interessante para evitar sobrecarga no servidor e até mesmo no navegador do usuário, e também para a barra de progresso ficar mais suave. Sendo assim, um segundo após a imagem ser salva no servidor, passamos para a próxima imagem.
Limpar
/*
Limpa os arquivos selecionados
*/
function limpar()
{
var input = $("#imagem");
input.replaceWith(input.val('').clone(true));
}
Utilizamos este método para limpar o input. É necessário para funcionar em versões anteriores do Internet Explorer.
O lado servidor
Recebemos a imagem no servidor em formato base64. Precisamos converte-lá em arquivo para salvá-la em disco.
<?php
// Recuperando imagem em base64
// Exemplo: data:image/png;base64,AAAFBfj42Pj4
$imagem = $_POST['imagem'];
// Separando tipo dos dados da imagem
// $tipo: data:image/png
// $dados: base64,AAAFBfj42Pj4
list($tipo, $dados) = explode(';', $imagem);
// Isolando apenas o tipo da imagem
// $tipo: image/png
list(, $tipo) = explode(':', $tipo);
// Isolando apenas os dados da imagem
// $dados: AAAFBfj42Pj4
list(, $dados) = explode(',', $dados);
// Convertendo base64 para imagem
$dados = base64_decode($dados);
// Gerando nome aleatório para a imagem
$nome = md5(uniqid(time()));
// Salvando imagem em disco
file_put_contents("../img/{$nome}.jpg", $dados);
Recebemos e armazenamos a imagem. Através das funções list() e explode() do PHP, separamos as informações contidas na imagem. Convertemos a imagem para arquivo através da função base64_decode(), geramos um nome único para ela e salvamos em uma pasta do servidor utilizando a função file_put_contents().
Conclusão
O redimensionamento de imagens no lado cliente reduz drasticamente o tempo de espera no envio das imagens, porém requer mais processamento da máquina do usuário, pois o redimensionamento está sendo feito pelo próprio navegador, mesmo assim é muito vantajoso quando trabalhamos com grande quantidade de imagens.
Vimos um exemplo simples que utiliza uma biblioteca pronta para fazer o redimensionamento, caso você precise de algo mais complexo será necessário entender melhor sobre a Canvas API do HTML5, que possibilita manipular diversos aspectos da imagem.
Enfim, espero que este exemplo tenha sido útil para você entender como funciona o redimensionamento de imagens no lado cliente.
Código fonte disponível em: https://github.com/rafaelcouto/redimensionar-imagem-antes-de-enviar-com-javascript/
Referências
- Vardy, Joel. JavaScript Image Upload. Disponível em <https://joelvardy.com/writing/javascript-image-upload>. Acesso em 06 de maio de 2015.
- Kartik. Clear HTML file input value in IE using javascript. Disponível em <http://webtips.krajee.com/clear-html-file-input-value-ie-using-javascript>. Acesso em 06 de maio de 2015.