Vinicius Quaiato

{tecnologia, conceitos, negócios, idéias, práticas, .NET, ruby, osx, ios e algo mais}

ASP.NET MVC upload de imagens + JCrop + Resize + progressbar + tudo assíncrono


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.

Os plugins

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)

Jquery.Form

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

JCrop

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.

Vamos baixar o JCrop aqui

Passo 1 - Realizar o upload da imagem

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).

O HTML inicial

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:

ubber elegant ubber elegant

Resumindo

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.

Voltar

Fork me on GitHub