Drag and Drop no HTML
Um recurso comum hoje em dia em muitos sites é o famoso Drag and Drop ou Arrastar e Soltar, mover um elemento até uma área ou até outro elemento é uma ação muito intuitiva e que os usuário já estão muito acostumados no desktop.
Para implementar este recurso utilizamos a API de Drag and Drop do próprio Javascript, que nos fornece diversos eventos e interfaces para trabalhar.
Para nosso exemplo vamos fazer duas colunas com itens que permitem serem arrastados de uma para outra. Para isso precisamos criar para cada coluna um div
contendo um titulo(h2
) e um outro div
que será a área contendo os itens que podem ser arrastados(nossa área de soltura).
O item a ser arrastado deve possuir o atributo draggable="true"
sendo isto o que permite que a ação de clicar e arrastar seja possível.
O HTML deve ficar mais ou menos assim
<div class="columns">
<div class="column">
<h2>Coluna 1</h2>
<div class="drop-area">
<div class="item" draggable="true">
Item 1
</div>
</div>
</div>
<div class="column">
<h2>Coluna 2</h2>
<div class="drop-area">
</div>
</div>
</div>
Antes de mais nada vamos entender os eventos de drag and drop:
dragstart
: Este evento é disparado quando o usuário inicia a ação de arrastar o elemento.drag
: Este evento é disparado quando o usuário está arrastando o elemento.dragend
: Este evento é disparado quando o usuário termina a ação de arrastar o elemento.dragenter
: Este evento é disparado em um elemento quando um outro elemento que está sendo arrasta entra sobre ele.dragover
: Este evento é disparado em um elemento quando um outro elemento que esta sendo arrastado é posicionado sobre ele. Este evento é disparado a cada aproximadamente 100 milissegundos enquanto o o elemento continuar sobre.dragleave
: Este evento é disparado quando o elemento que esta sendo arrastado deixa o contato com o elemento.drop
: Este evento é disparado quando o elemento que está sendo arrastado é solto sobre o elemento.
Conhecendo apenas estes eventos já é possível fazer nosso exemplo, simplesmente pegando e marcado o elemento que está sendo arrastado(através do evento dragstart
) e adicioná-lo a nova área quando estiver sobre ou for solto sobre ela. Veja como ficaria o Javasript de drag and drop:
const items = document.querySelectorAll('.item');
const dropAreas = document.querySelectorAll('.drop-area');
items.forEach((item) => {
item.addEventListener('dragstart', startDrag);
item.addEventListener('dragend', endDrag);
});
function startDrag(ev){
const itemDrag = ev.currentTarget;
itemDrag.classList.add('moving');
ev.dataTransfer.effectAllowed = "move";
dropAreas.forEach( (area) =>{
area.classList.add('highlight-drop-area');
});
}
function endDrag(ev){
const itemDrag = ev.currentTarget;
itemDrag.classList.remove('moving');
dropAreas.forEach( (area) =>{
area.classList.remove('highlight-drop-area');
});
}
dropAreas.forEach( (area) =>{
area.addEventListener('dragover', dragoverArea)
});
function dragoverArea(ev){
ev.preventDefault();
const dropArea = ev.currentTarget;
const itemDrag = document.querySelector('.moving');
dropArea.appendChild(itemDrag);
}
Na linha 1 pegamos um array com todos o elementos da classe .item
, eles serão os elementos que poderão ser arrastados. Na linha 2 pegamos todas as áreas de soltura, ou seja, os elementos da classe .drop-area
.
Na linha 4 percorremos todos os elementos da classe .item
e adicionamos para eles dois eventos: um evento de dragstart
que irá chamar a função startDrag
(linha 9) quando a ação de arrastar se iniciar e um evento de dragend
que irá chamar a função endDrag
(linha 19) que será chamada quando a ação de arrastar for encerrada.
Na função startDrag
pegamos o elemento que sofreu a ação de arrastar através do objeto de evento na linha 10 e adicionamos a classe .moving
, utilizaremos essa classe como marcador de qual elemento estamos arrastando. Na linha 12 configuramos o effectAllowed
do objeto dataTransfer
do evento para "move"
. O atributo effectAllowed
controla o feedback visual da ação. Na linha 14 percorremos todas as areas de soltura(elementos da classe .drop-area
) e adicionamos uma .highlight-drop-area
que possui uma cor de fundo diferente para fornecer um auxilio visual de onde soltar o elemento.
Na função endDrag
apenas removemos as classes adicionadas pelo startDrag
limpando os estilos.
Na linha 29 percorremos todos os elementos de área de soltura adicionando um evento de dragover
que será disparado quando um elemento .item
for arrastado sobre ele chamando assim a função dragoverArea
.
Na função dragoverArea
(linha 33) primeiro prevenimos o processamento adicional de eventos através do preventDefault()
, depois pegamos o elemento de área de soltura em que o elemento que está sendo arrastado esta em cima através do currentTarget
do objeto de evento(linha 35). Na linha 37 pegamos o elemento que marcamos com a classe .moving
e na linha 38 adicionamos ele na nova área, assim movendo-o para a nova área. Quando o elemento for solto ele ficará nesta área.
Veja o exemplo de drag and drop.
Drag and drop de arquivos
Outro recurso interessante com o drag and drop é permitir que os usuário arraste um arquivo do desktop para uma área da página para fazer o upload.
Primeiro iremos precisar definir de uma área de soltura no HTML e onde iremos exibir as informações do arquivo. No nosso exemplo ficaria assim.
<div class="container">
<div class="area-drop">
Arraste o arquivo para este local
</div>
<div class="file-data">
<div class="name"></div>
<div class="size"></div>
<div class="type"></div>
<img id="imagem" />
</div>
</div>
Com o HTML iremos para o Javascript:
const areaDrop = document.querySelector('.area-drop');
// Impedir o comportamento padrão (impedir que o arquivo seja aberto)
document.addEventListener('dragover', (ev) => {
ev.preventDefault();
});
areaDrop.addEventListener('drop', dropHandler);
areaDrop.addEventListener('dragenter', dragenterHandler);
areaDrop.addEventListener('dragleave', dragleaveHandler);
function dropHandler(ev) {
ev.preventDefault();
const areaDrop = ev.currentTarget;
areaDrop.classList.remove('over');
const nameElement = document.querySelector('.name');
const sizeElement = document.querySelector('.size');
const typeElement = document.querySelector('.type');
nameElement.innerHTML = 'Name: ' + ev.dataTransfer.files[0].name;
sizeElement.innerHTML = 'Size: ' + ev.dataTransfer.files[0].size;
typeElement.innerHTML = 'Type: ' + ev.dataTransfer.files[0].type;
const type = ev.dataTransfer.files[0].type;
if(['image/jpg', 'image/png'].includes(type)){
const reader = new FileReader();
reader.onload = (event) => {
let data = event.target.result;
document.querySelector("#imagem").src = 'data:' + type + ';base64,' + btoa(data);
};
reader.readAsBinaryString(ev.dataTransfer.files[0]);
}
}
function dragenterHandler(ev) {
ev.preventDefault();
const areaDrop = ev.currentTarget;
areaDrop.classList.add('over');
}
function dragleaveHandler(ev) {
ev.preventDefault();
const areaDrop = ev.currentTarget;
areaDrop.classList.remove('over');
}
Na linha 1 pegamos o elemento da classe .drop-area
que é nossa área de soltura.
Na linha 4 adicionamos um evento de dragover
a todo documento, e simplesmente cancelamos os comportamentos padrões desta ação. Isto é muito importante pois a ação padrão do dragover
do documento é abrir o documento o que impediria os outro eventos de funcionar como queremos.
Na linha 8 adicionamos ao elemento de dropArea
(nossa área de soltura) o evento drop
que irá chamar a função dropHandler
(linha 12) quer irá fazer todo o trabalho. Também adicionamos logo após os eventos dragenter
e dragleave
, simplesmente adicionando uma classe na dropArea
para fornecer um auxilio visual ao usuário.
Na função dropHandler
começamos impedindo o processamento das ações padrões através do preventDefault
. Depois pegamos o elemento da área de soltura(linha 16) e removemos a classe decorativa. Em seguida pegamos os elementos onde iremos exibir as informações do arquivo dropado(linhas 19,20 e 21).
O objeto de evento ev
possui um atributo dataTransfer
que contem informações sobre o objeto a ser arrastado. Entre seus atributos temos um array files
contendo os arquivo arrastados, no nosso exemplo só estamos tratando de um arquivo então simplesmente acessamos ev.dataTransfer.files[0]
. Este objeto possui os atributos name
, size
e type
, respectivamente o nome do arquivo, o tamanho e o mimetype, então atribuímos seus valores aos devidos elemento da página nas linhas 23,23 e 25.
Caso o arquivo seja uma imagem(seu type seja 'image/jpg'
ou'image/png'
) lemos o arquivo, convertemos para base64 e exibimos em uma tag img
, já vimos isso em outro post.
Para ver o exemplo funcionando acesse. E se quiser baixar os exemplo.
Bom era isso não é muito difícil de implementar e o resultado é bem interessante. E como sempre esta foi apenas uma introdução para mais detalhes verifique a documentação da API. T++.