← Todas as postagens
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.

Download

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.

<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:

.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;
	
	-webkit-transition: .3s all;
	-moz-transition: .3s all;
	-o-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.


.area-upload input{
	position: absolute;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
	width: 100%;
	-webkit-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.

.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;
	
	-webkit-transition: .2s all;
	-moz-transition: .2s all;
	-o-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.

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.

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.

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.


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.

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

Download

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.

Comentários

Carregando comentários…