{tecnologia, conceitos, negócios, idéias, práticas, .NET, ruby, osx, ios e algo mais}
04/05/2012
Essa é uma das features que qualquer sistema web público precisa:
Usuário precisa fazer upload de uma foto para o seu perfil
Até aí tudo bem, tudo simples. O problema é que o layout das aplicações exigem uma foto de perfil quadrada. Até aí tudo bem também. Mas o que acontece quando o usuário faz upload de uma imagem que não é quadrada? Vamos simplesmente redimensionar? Eu não acho essa uma boa solução: as imagens retangulares ficam feias demais se simplesmente redimensionadas para um quadrado. Não dá para simplesmente fazer um resize proporcional, afinal é um retângulo.
Para "complicar" um pouco mais as coisas eu quero que tudo seja feito de forma assíncrona e que tenhamos uma barra de progresso.
Para fazer esse trabalho sujo, client side, vamos usar alguns plugins para facilitar nossa vida.
Juntar estes plugins para essa solução é algo bastante simples e com pouco código podemos realizar o trabalho. (por favor atentem que estou blogando isso exatamente em seguida de ter terminado de implementar isso, então nenhum código recebeu refatoração, é preciso melhorá-lo, claro)
Para simplificar tudo quero fazer, quero que todo esse processo seja realizado de forma assíncrona. Ou seja o usuário faz o upload de uma foto inicial, consegue realizar o crop e então submete novamente para que o crop seja salvo.
Pesquisando encontrei o Jquery.Form.
Este plugin é bastante simples e nos permite trabalhar com formulários de maneira assíncrona. <sarcasm>É quase um UpdatePanel</sarcasm>
É também este o plugin responsável pela barra de progresso :D
Vamos baixar o Jquery.Form plugin aqua no site do plugin
Acho que é o JCrop é o plugin JQuery mais famoso para crop de imagens. É bastante simples de utilizar. Esse eu conheço já de longa data, então eu nem pesquisei alternativas.
O primeiro passo então para nosso upload com crop e resize assíncrono lindo de morrer é conseguir uma imagem quadrada que nos possibilite um resize sem deformações medonhas.
Para fazer isso precisamos realizar o upload de uma imagem e permitir que o usuário selecione uma parte quadrada desta imagem. Após o upload precisamos exibir a imagem para que o mesmo possa selecionar uma área dentro dessa e então realizar o crop(recorte).
Vamos criar um form e aplicar o jquery-form nele:
1 <form action='@Url.Action("Upload")' method="post" enctype="multipart/form-data" name="imagem_original">
2 <input type="file" name="imagem" />
3 <input type="submit" value="upload to server" class="hidden" id="upload" />
4 </form>
5 <div class="progress">
6 <div class="bar"></div>
7 <div class="percent">0%</div>
8 </div>
9 <img src="" class="hidden" id="imagem_crop" />
Esse form por si só não faz nada assíncrono, então vamos adicionar o seguinte em nossa página. Reparem apenas que na linha 5 eu adiciono um div que funcionará como progress bar. Na linha 9 eu coloquei uma imagem escondida. Será nesta tag %lt;img que exibiremos a imagem após o upload, para realizar o crop.
1 <script src='@Url.Content("~/Scripts/jquery-1.7.2.min.js")' type="text/javascript"></script>
2 <script src='@Url.Content("~/Scripts/jquery.form.js")' type="text/javascript"></script>
3 <script>
4 (function () {
5 var bar = $('.bar');
6 var percent = $('.percent');
7 var status = $('#status');
8
9 $('input[name=imagem]').live('change', function () {
10 $("#upload").click();
11 });
12
13 $('form[name=imagem_original]').ajaxForm({
14 dataType: 'json',
15 beforeSend: function () {
16 status.empty();
17 var percentVal = '0%';
18 bar.width(percentVal);
19 percent.html(percentVal);
20 },
21 uploadProgress: function (event, position, total, percentComplete) {
22 var percentVal = percentComplete + '%';
23 bar.width(percentVal);
24 percent.html(percentVal);
25 },
26 success: function (data) {
27 $('input[name=url]').val(data.url);
28 $("#imagem_crop").attr("src", data.url);
29 $("#imagem_crop").removeClass("hidden");
30 }
31 });
32 })();
33 </script>
Esse código não possui nenhum mistério. Nas linhas 5, 6 e 7 apenas obtenho alguns elementos e coloco em variáveis para reutilizarmos depois.
Nas linhas 9, 10 e 11 disparamos o submit do form automaticamente sempre que um arquivo novo é selecionado, por isso mantive o botão de upload escondido no formulário, mas poderia nem ter o botão lá.
Na sequência, nas linhas 13 e 14 o jquery-form configura nosso formulário como um formulário ajax, utilizando o retorno do servidor como json.
A função beforeSend na linha 15 é executada antes mesmo que o envio dos dados ocorra (dãr!) e o que ela faz é apenas zerar uma barra de progesso (100% feita em html).
A função uploadProgress na linha 21 é chamada de tempos em tempos com o status de progresso do upload e com isso conseguimos atualizar a barra de progresso.
Já a função success na linha 26 é chamada com o retorno do servidor, neste caso um objeto json contendo a url de onde a imagem foi salva. Nesta função eu já preparo algumas coisas para iniciarmos o crop: na linha 27 eu coloco a url da imagem em um input hidden, pois não faremos o upload da imagem novamente, vamos carregá-la no servidor direto da url. Também coloco a url da imagem no atributo src de uma tag <img e deixo esta imagem visível na tela pois será nela que faremos o crop.
O código server no ASP.NET MVC para isso é algo como:
1 [HttpPost]
2 public ActionResult Upload() {
3 var nomeOriginal = Request.Files[0].FileName;
4 var nomeFinal = "../uploads/imagem" + Path.GetExtension(nomeOriginal);
5 var pathFinal = Server.MapPath(nomeFinal);
6
7 Request.Files[0].SaveAs(pathFinal);
8 return Json(new { url = nomeFinal });
9 }
Nada anormal (removi uma série de tratamentos, validações, etc, para manter o código breve e acho que ele ainda está funcionando :P).
Feito tudo isso devemos ter uma página capaz de realizar um upload assíncrono, com barra de progresso e exibindo o resultado do upload em uma tag <img mais ou menos como a imagem abaixo:
No próximo post mostrarei como colocarmos o JCrop para funcionar e realizar o upload das informações para recortar a imagem no server side.
Abraços,
Vinicius Quaiato.