Desenvolvimento,Design,Programação,Tutoriais -
Como criar um uploader drag & drop, AJAX com porcentagem e Javascript puro?
Eu sei, o título é grande, mas trata-se de uma tecnologia muito útil quanto a usabilidade. A ideia é criar, da forma mais simples possível um uploader que mostre a porcentagem do que está sendo enviado, com múltiplos arquivos. Porém, não apenas isso, mas da forma mais pura possível, sem a necessidade de frameworks, bibliotecas de terceiros ou pre-processadores. Em nosso tutorial, usaremos PHP no back-end, mas você pode usar a linguagem que melhor lhe adequar. Abaixo você pode testar um exemplo:
DISCLAIMER: Para preservar você, o blog e a mim, o upload.php do exemplo não está fazendo upload de verdade, então você não vai ter acesso ao arquivo no final, porém, no link do github está o código funcional.
HTML
Antes de prosseguir, vou alertar que estou usando o font-awesome no exemplo, apenas para não ter que fazer nenhum upload de imagens por hora, mas ele não é obrigado para o que vamos fazer.
Iniciaremos com um HTML bem simples. Vamos definir apenas a área de upload, da lista e uma área onde ficará o input. Esse input deve ser do tipo file e deve estar com o atributo mutiple definido. Nada muito especial por enquanto, pois nosso trunfo está mais no CSS e Javascript.
xxxxxxxxxx <div class="area-upload"> <label for="upload-file" class="label-upload"> <i class="fas fa-cloud-upload-alt"></i> <div class="texto">Clique ou arraste o arquivo</div> </label> <input type="file" accept="image/jpg,image/png" id="upload-file" multiple/> <div class="lista-uploads"> </div> </div>
CSS3
O CSS que usaremos tem pontos específicos aos quais devemos prestar atenção. Por isso, vamos ver por trechos:
xxxxxxxxxx .area-upload{ box-shadow: 0 5px 20px rgba(0,0,0,.2); margin: 20px auto; padding: 20px; box-sizing: border-box; width: 100%; max-width: 700px; position: relative;}.area-upload label.label-upload{ border: 2px dashed #0d8acd; min-height: 200px; text-align: center; width: 100%; display: flex; justify-content: center; flex-direction: column; color: #0d8acd; position: relative; transition: .3s all; transition: .3s all; transition: .3s all; transition: .3s all;}.area-upload label.label-upload.highlight{ background-color: #fffdaa;}.area-upload label.label-upload *{ pointer-events: none;}
A área de upload é definida pelo CSS a partir de um espaço abrangente. O label, vai ser a referência que vamos usar para atingir toda a área pré-determinada e é o que vai também receber o estilo que indicará onde e quando pode soltar o objeto. Dessa forma, deve estar com um position: absolute, pegando toda a área do upload. Um detalhe importante é que esse label não pode possuir filhos com eventos do mouse, pois isso pode acarretar em funcionamento inadequado ao fazer um hover em uma área não indicada.
A área marcada como hightlight é justamente para demonstrar ao usuário que ele já pode soltar o arquivo.
xxxxxxxxxx .area-upload input{ position: absolute; left: 0; top: 0; right: 0; bottom: 0; width: 100%; appearance: none; opacity: 0;}
O input vai nos servir de área de drop. Isso já é um padrão tanto para Windows, MacOS e Linux – de receber um (ou mais arquivos) arrastando os dados para o botão. Retirando a aparência padrão, podemos nos aproveitar dessa função e economizar várias linhas de código do Javascript. Não havendo a necessidade de configurarmos eventos de arquivos.
xxxxxxxxxx .area-upload .lista-uploads .barra{ background-color: #e6e6e6; margin: 10px 0; width: 100%; position: relative;}.area-upload .lista-uploads .barra .fill{ background-color: #a1f7ff; position: absolute; top: 0; left: 0; bottom: 0; min-width: 0; transition: .2s all; transition: .2s all; transition: .2s all; transition: .2s all;}.area-upload .lista-uploads .barra.complete .fill{ background-color: #bcffdf;}.area-upload .lista-uploads .barra .text{ z-index: 10; text-align: center; padding: 3px 5px; box-sizing: border-box; position: relative; width: 100%; color: black; font-size: 12px;}.area-upload .lista-uploads .barra .text a{ color: black; font-weight: bold;}.area-upload .lista-uploads .barra.error .fill{ background-color: #c02929; color: white; min-width: 100%;}.area-upload .lista-uploads .barra.error .text{ color: white;}
Talvez a área que possa confundir um pouco mais, principalmente os mais novatos, seja o CSS referente as barras de loading. As barras são definidas como relative e seu conteúdo como absolute. Dessa forma, podemos herdar a porcentagem do upload e usá-la diretamente no min-width do elemento. Usamos min-width, ao invés de width para utilizar o transition, dando uma leveza no upload ao invés de pequenos estalos.
Javascript
Inicialmente iremos definir os efeitos de drag and drop. Utilizaremos os eventos específicos para adicionar ou retirar o css de hightlight. Aquele que falamos acima ao montar o CSS.
xxxxxxxxxx let drop_ = document.querySelector('.area-upload #upload-file');drop_.addEventListener('dragenter', function(){ document.querySelector('.area-upload .label-upload').classList.add('highlight');});drop_.addEventListener('dragleave', function(){ document.querySelector('.area-upload .label-upload').classList.remove('highlight');});drop_.addEventListener('drop', function(){ document.querySelector('.area-upload .label-upload').classList.remove('highlight');});
Agora precisamos validar os dados antes de colocar em nosso servidor. Compusemos uma validação que verifica o tipo de arquivo e a quantidade máxima de 2MB. Essa validação é totalmente no front-end. É recomendado que você também faça uma validação posterior no back-end para evitar fraudes. Essa função receberá como parâmetro um arquivo, o qual faremos a validação.
xxxxxxxxxx function validarArquivo(file){ console.log(file); // Tipos permitidos var mime_types = [ 'image/jpeg', 'image/png' ]; // Validar os tipos if(mime_types.indexOf(file.type) == -1) { return {"error" : "O arquivo " + file.name + " não permitido"}; } // Apenas 2MB é permitido if(file.size > 2*1024*1024) { return {"error" : file.name + " ultrapassou limite de 2MB"}; } // Se der tudo certo return {"success": "Enviando: " + file.name};}
Para enviar os arquivos, usaremos uma função AJAX simples. Fazemos um request no método POST. Nesta função, iremos capturar também a porcentagem do upload e atualizaremos a barra a partir desta, através do evento progress. Essa nossa função receberá o índice, para identificar o arquivo que está sendo enviado e a barra que será modificada.
xxxxxxxxxx function enviarArquivo(indice, barra){ var data = new FormData(); var request = new XMLHttpRequest(); //Adicionar arquivo data.append('file', document.querySelector('#upload-file').files[indice]); // AJAX request finished request.addEventListener('load', function(e) { // Resposta if(request.response.status == "success"){ barra.querySelector(".text").innerHTML = "<a href=\"" + request.response.path + "\" target=\"_blank\">" + request.response.name + "</a> <i class=\"fas fa-check\"></i>"; barra.classList.add("complete"); }else{ barra.querySelector(".text").innerHTML = "Erro ao enviar: " + request.response.name; barra.classList.add("error"); } }); // Calcular e mostrar o progresso request.upload.addEventListener('progress', function(e) { var percent_complete = (e.loaded / e.total)*100; barra.querySelector(".fill").style.minWidth = percent_complete + "%"; }); //Resposta em JSON request.responseType = 'json'; // Caminho request.open('post', 'upload.php'); request.send(data);}
Por fim, iremos juntar um pouco de cada coisa que fizemos, adicionando uma função em um evento change do input do upload. Neste momento, também é feito o laço dentre o(s) arquivo(s) selecionado(s) para validar e, se for válido, fazer o upload, utilizando os outros métodos acima mencionados. É neste momento também que serão criadas dinamicamente as barras: uma para cada arquivo.
xxxxxxxxxx document.querySelector('#upload-file').addEventListener('change', function() {var files = this.files;for(var i = 0; i < files.length; i++){var info = validarArquivo(files[i]); //Criar barra var barra = document.createElement("div"); var fill = document.createElement("div"); var text = document.createElement("div"); barra.appendChild(fill); barra.appendChild(text); barra.classList.add("barra"); fill.classList.add("fill"); text.classList.add("text"); if(info.error == undefined){ text.innerHTML = info.success; enviarArquivo(i, barra); //Enviar }else{ text.innerHTML = info.error; barra.classList.add("error"); } //Adicionar barra document.querySelector('.lista-uploads').appendChild(barra);};});
PHP
E pra terminar com chave de ouro, um PHPzinho que receberá esses arquivos e retornará um json com a confirmação de sucesso ou erro. Não aplicamos muitas validações nesse PHP pois nosso foco era neste exemplo está no front-end, mas é suficiente para que você compreenda sua funcionalidade.
xxxxxxxxxx <?phpini_set('upload_max_filesize', '2M');header('Content-Type: application/json');$file = $_FILES['file'];$ret = [];if(move_uploaded_file($file['tmp_name'],'uploads/'.$file['name'])){ $ret["status"] = "success"; $ret["path"] = 'uploads/'. $file['name']; $ret["name"] = $file['name'];}else{ $ret["status"] = "error"; $ret["name"] = $file['name'];}echo json_encode($ret, JSON_PRETTY_PRINT);?>
E, como sempre, você pode baixar o código inteiro no Github e usar direto na sua aplicação.
Gostou? Se sim, compartilha com seus amiguinhos interessados na área e curte a página no Facebook. O exemplo acima está levemente modificado para que funcione melhor aqui no site. Entre também no grupo de Design e Desenvolvimento. O link está abaixo do post.