A API HTML5 Drag and Drop

Esta postagem explica os conceitos básicos do recurso de arrastar e soltar.

Criar conteúdo arrastável

Na maioria dos navegadores, as seleções de texto, imagens e links são arrastáveis por padrão. Por exemplo, se você arrastar um link em uma página da Web, vai aparecer uma pequena caixa com um título e URL que podem ser soltos na barra de endereço ou na área de trabalho para criar um atalho ou navegar até o link. Para tornar outros tipos de conteúdo arrastáveis, use as APIs HTML5 Drag and Drop.

Para tornar um objeto arrastável, defina draggable=true nesse elemento. Você pode arrastar para tudo, incluindo imagens, arquivos, links, arquivos ou qualquer marcação na página.

O exemplo a seguir cria uma interface para reorganizar colunas que foram dispostas com a grade CSS. A marcação básica para as colunas tem esta aparência, com o atributo draggable para cada coluna definido como true:

<div class="container">
  <div draggable="true" class="box">A</div>
  <div draggable="true" class="box">B</div>
  <div draggable="true" class="box">C</div>
</div>

Este é o CSS para os elementos container e box. O único CSS relacionado ao recurso de arrastar é a propriedade cursor: move. O restante do código controla o layout e o estilo dos elementos do contêiner e da caixa.

.container {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
}

.box {
  border: 3px solid #666;
  background-color: #ddd;
  border-radius: .5em;
  padding: 10px;
  cursor: move;
}

Agora você pode arrastar os itens, mas nada mais acontece. Para adicionar um comportamento, é necessário usar a API JavaScript.

Detectar eventos de arrastar

Para monitorar o processo de arrastar, você pode detectar qualquer um dos seguintes eventos:

Para processar o fluxo de arrastar, você precisa de algum tipo de elemento de origem (em que a ação de arrastar começa), do payload de dados (o que está sendo arrastado) e de um destino (uma área para capturar a ação de soltar). O elemento de origem pode ser quase qualquer tipo de elemento. O destino é a zona ou o conjunto de zonas que aceita os dados que o usuário está tentando soltar. Nem todos os elementos podem ser destinos. Por exemplo, o destino não pode ser uma imagem.

Iniciar e encerrar uma sequência de arrastar

Depois de definir atributos draggable="true" no seu conteúdo, anexe um manipulador de eventos dragstart para iniciar a sequência de arrastar para cada coluna.

Esse código define a opacidade da coluna como 40% quando o usuário começa a arrastá-la e a retorna a 100% quando o evento de arrastar termina.

function handleDragStart(e) {
  this.style.opacity = '0.4';
}

function handleDragEnd(e) {
  this.style.opacity = '1';
}

let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
  item.addEventListener('dragstart', handleDragStart);
  item.addEventListener('dragend', handleDragEnd);
});

O resultado pode ser visto na demonstração do Glitch a seguir. Arraste um item, e a opacidade dele muda. Como o elemento de origem tem o evento dragstart, definir this.style.opacity como 40% dá ao usuário um feedback visual de que esse elemento é a seleção atual que está sendo movida. Quando você solta o item, o elemento de origem retorna a 100% de opacidade, mesmo que você ainda não tenha definido o comportamento de soltar.

Adicionar outros sinais visuais

Para ajudar o usuário a entender como interagir com sua interface, use os manipuladores de eventos dragenter, dragover e dragleave. Neste exemplo, as colunas são destinos de soltar, além de serem arrastáveis. Ajude o usuário a entender isso fazendo com que a borda seja tracejada quando um item for arrastado sobre uma coluna. Por exemplo, no seu CSS, você pode criar uma classe over para elementos que são destinos de soltar:

.box.over {
  border: 3px dotted #666;
}

Em seguida, no JavaScript, configure os manipuladores de eventos, adicione a classe over quando a coluna for arrastada e a remova quando o elemento arrastado sair. No gerenciador dragend, removemos as classes ao final da arrasto.

document.addEventListener('DOMContentLoaded', (event) => {

  function handleDragStart(e) {
    this.style.opacity = '0.4';
  }

  function handleDragEnd(e) {
    this.style.opacity = '1';

    items.forEach(function (item) {
      item.classList.remove('over');
    });
  }

  function handleDragOver(e) {
    e.preventDefault();
    return false;
  }

  function handleDragEnter(e) {
    this.classList.add('over');
  }

  function handleDragLeave(e) {
    this.classList.remove('over');
  }

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });
});

Há alguns pontos que valem a pena abordar neste código:

  • A ação padrão para o evento dragover é definir a propriedade dataTransfer.dropEffect como "none". A propriedade dropEffect será abordada mais adiante nesta página. Por enquanto, saiba que ele impede o disparo do evento drop. Para substituir esse comportamento, chame e.preventDefault(). Outra prática recomendada é retornar false nesse mesmo gerenciador.

  • O manipulador de eventos dragenter é usado para alternar a classe over em vez de dragover. Se você usar dragover, o evento será disparado repetidamente enquanto o usuário mantiver o item arrastado sobre uma coluna, fazendo com que a classe CSS seja alternada repetidamente. Isso faz com que o navegador realize muitas tarefas de renderização desnecessárias, o que pode afetar a experiência do usuário. É altamente recomendável minimizar os redesenhos e, se você precisar usar dragover, considere limitar ou devolver seu listener de eventos.

Conclua a migração

Para processar a ação de soltar, adicione um listener de eventos para o evento drop. No gerenciador drop, você precisará impedir o comportamento padrão do navegador para quedas, que normalmente é algum tipo de redirecionamento irritante. Para fazer isso, chame e.stopPropagation().

function handleDrop(e) {
  e.stopPropagation(); // stops the browser from redirecting.
  return false;
}

Registre o novo gerenciador junto com os outros:

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });

Se você executar o código neste ponto, o item não será solto no novo local. Para fazer isso, use o objeto DataTransfer.

A propriedade dataTransfer contém os dados enviados em uma ação de arrastar. dataTransfer é definido no evento dragstart e lido ou processado no evento de soltar. Chamar e.dataTransfer.setData(mimeType, dataPayload) permite definir o tipo MIME e o payload de dados do objeto.

Neste exemplo, vamos permitir que os usuários reorganizem a ordem das colunas. Para fazer isso, primeiro você precisa armazenar o HTML do elemento de origem quando a ação de arrastar começar:

function handleDragStart(e) {
  this.style.opacity = '0.4';

  dragSrcEl = this;

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}

No evento drop, você processa o descarte da coluna definindo o HTML da coluna de origem como o HTML da coluna de destino em que você descartou os dados. Isso inclui verificar se o usuário não está voltando para a mesma coluna da qual ele foi arrastado.

function handleDrop(e) {
  e.stopPropagation();

  if (dragSrcEl !== this) {
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }

  return false;
}

Confira o resultado na demonstração a seguir. Para que isso funcione, você precisa de um navegador para computador. A API Drag and Drop não é compatível com dispositivos móveis. Arraste e solte a coluna A em cima da coluna B e observe como elas mudam de lugar:

Mais propriedades de arrastar

O objeto dataTransfer expõe propriedades para fornecer feedback visual ao usuário durante o processo de arrastar e controlar como cada destino de soltar responde a um tipo de dados específico.

  • dataTransfer.effectAllowed restringe o "tipo de arrastar" que o usuário pode realizar no elemento. Ele é usado no modelo de processamento de arrastar e soltar para inicializar o dropEffect durante os eventos dragenter e dragover. A propriedade pode ter os seguintes valores: none, copy, copyLink, copyMove, link, linkMove, move, all e uninitialized.
  • O dataTransfer.dropEffect controla o feedback que o usuário recebe durante os eventos dragenter e dragover. Quando o usuário mantém o ponteiro sobre um elemento de destino, o cursor do navegador indica o tipo de operação que vai ocorrer, como uma cópia ou movimentação. O efeito pode usar um dos seguintes valores: none, copy, link, move.
  • e.dataTransfer.setDragImage(imgElement, x, y) significa que, em vez de usar o feedback de "imagem fantasma" padrão do navegador, você pode definir um ícone de arrastar.

Upload do arquivo

Este exemplo simples usa uma coluna como origem e destino da ação de arrastar. Isso pode acontecer em uma interface que solicita que o usuário reorganize os itens. Em algumas situações, o destino e a origem da ação de arrastar podem ser tipos de elementos diferentes, como em uma interface em que o usuário precisa selecionar uma imagem como a principal de um produto arrastando a imagem selecionada para um destino.

O recurso de arrastar e soltar é frequentemente usado para permitir que os usuários arrastem itens da área de trabalho para um aplicativo. A principal diferença está no gerenciador drop. Em vez de usar dataTransfer.getData() para acessar os arquivos, os dados deles ficam contidos na propriedade dataTransfer.files:

function handleDrop(e) {
  e.stopPropagation(); // Stops some browsers from redirecting.
  e.preventDefault();

  var files = e.dataTransfer.files;
  for (var i = 0, f; (f = files[i]); i++) {
    // Read the File objects in this FileList.
  }
}

Mais informações sobre isso estão em Arrastar e soltar personalizado.

Mais recursos