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_atual
for 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 (dataURL
oufile
) 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.