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
<?php
ini_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.