Drag “n” Soltar com eventos de mouse

Drag’n’Drop é uma ótima solução de interface. Pegar algo, arrastar e soltar é uma maneira clara e simples de fazer muitas coisas, desde copiar e mover documentos (como em gerenciadores de arquivos) até ordenar (soltar itens em um carrinho).

No HTML moderno padrão, há uma seção sobre arrastar e soltar com eventos especiais, como dragstart, dragend e assim por diante.

Esses eventos nos permitem oferecer suporte a tipos especiais de arrastar e soltar, como manipular o arrastamento de um arquivo do gerenciador de arquivos do sistema operacional e soltá-lo na janela do navegador. Então, o JavaScript pode acessar o conteúdo de tais arquivos.

Mas os eventos de arrastar nativos também têm limitações. Por exemplo, não podemos evitar arrastar de uma determinada área. Além disso, não podemos arrastar apenas “horizontal” ou “vertical”. E há muitas outras tarefas de arrastar e soltar que não podem ser feitas com eles. Além disso, o suporte do dispositivo móvel para tais eventos é muito fraco.

Então, aqui vamos ver como implementar Drag’n’Drop usando eventos de mouse.

Algoritmo Drag’n’Drop

O algoritmo Drag’n’Drop básico se parece com este:

  1. On mousedown – prepare o elemento para mover, se necessário (talvez criar um clone dele, adicionar uma classe a ele ou qualquer outra coisa).
  2. Em seguida, em mousemove mova-o alterando left/top com position:absolute.
  3. Ativado mouseup – execute todas as ações relacionadas ao término do arrasto ‘drop.

Estes são os princípios básicos. Mais tarde, veremos como fazer outros recursos, como destacar os elementos subjacentes atuais enquanto arrastamos sobre eles.

Aqui está a implementação de arrastar uma bola:

Se executarmos o código, podemos notar algo estranho. No início do arrastar e soltar, a bola “bifurca”: começamos a arrastar seu “clone”.

Aqui está um exemplo em ação:

Tente arrastar e soltar com o mouse e você verá esse comportamento.

Isso porque o navegador tem seu próprio suporte de arrastar e soltar para imagens e alguns outros elementos. Ele é executado automaticamente e entra em conflito com o nosso.

Para desativá-lo:

Agora tudo ficará bem.

Em ação:

Outro aspecto importante – rastreamos mousemove em document, não em ball. À primeira vista, pode parecer que o mouse está sempre sobre a bola, e podemos colocar mousemove nele.

Mas, como lembramos, mousemove dispara com frequência, mas não para cada pixel. Assim, após um movimento rápido, o ponteiro pode pular da bola em algum lugar no meio do documento (ou até mesmo para fora da janela).

Portanto, devemos ouvir document para pegá-la.

Posicionamento correto

Nos exemplos acima a bola é sempre movida de forma que seu centro fique sob o ponteiro:

Nada mal, mas tem um efeito colateral. Para iniciar o arrastar e soltar, podemos mousedown em qualquer lugar da bola. Mas se “pegá-lo” de sua borda, então a bola de repente “pula” e fica centralizada sob o ponteiro do mouse.

Seria melhor se mantivéssemos o deslocamento inicial do elemento em relação ao ponteiro.

Por exemplo, se começarmos a arrastar pela borda da bola, o ponteiro deve permanecer sobre a borda enquanto arrasta.

Vamos atualizar nosso algoritmo:

O código final com melhor posicionamento:

Em ação (dentro de <iframe>) :

A diferença é especialmente perceptível se arrastarmos a bola pelo canto inferior direito. No exemplo anterior, a bola “pula” sob o ponteiro. Agora, ela segue fluentemente o ponteiro da posição atual.

Alvos de queda potenciais (droppables)

Nos exemplos anteriores, a bola poderia ser descartado apenas “em qualquer lugar” para ficar. Na vida real, geralmente pegamos um elemento e jogamos em outro. Por exemplo, um “arquivo” em uma “pasta” ou qualquer outra coisa.

Falando em abstrato, pegamos um elemento “arrastável” e o soltamos no elemento “soltável”.

Nós precisa saber:

  • onde o elemento foi solto no final de Drag’n’Drop – para fazer a ação correspondente,
  • e, de preferência, saber o que podemos soltar estamos se arrastando para realçá-lo.

A solução é meio interessante e um pouco complicada, então vamos abordá-la aqui.

O que pode ser a primeira ideia? Provavelmente para definir mouseover/mouseup manipuladores em possíveis droppables?

Mas isso não funciona.

O problema é que, enquanto estamos arrastando, o elemento arrastável está sempre acima de outros elementos.E os eventos do mouse acontecem apenas no elemento superior, não naqueles abaixo dele.

Por exemplo, abaixo estão dois <div> elementos, um vermelho sobre o azul um (cobre totalmente). Não há como capturar um evento no azul, porque o vermelho está no topo:

O mesmo com um elemento arrastável. A bola está sempre por cima dos outros elementos, por isso os eventos acontecem nela. Sejam quais forem os gerenciadores que definirmos em elementos inferiores, eles não funcionarão.

É por isso que a ideia inicial de colocar gerenciadores em possíveis droppables não funciona na prática. Eles não serão executados.

Então, o que fazer?

Há um método chamado document.elementFromPoint(clientX, clientY). Ele retorna o elemento mais aninhado em determinadas coordenadas relativas à janela (ou null se as coordenadas fornecidas estiverem fora da janela).

Podemos usá-lo em qualquer um dos nossos manipuladores de eventos do mouse para detectar o potencial droppable sob o ponteiro, como este:

Observação: precisamos esconder a bola antes da chamada (*). Caso contrário, geralmente teremos uma bola nessas coordenadas, pois é o elemento superior sob o ponteiro: elemBelow=ball. Então, nós o escondemos e imediatamente mostramos novamente.

Podemos usar esse código para verificar sobre qual elemento estamos “voando” a qualquer momento. E lidar com a queda quando isso acontecer.

Um código estendido de onMouseMove para encontrar elementos “soltáveis”:

No exemplo abaixo, quando a bola é arrastada sobre a baliza de futebol, a baliza é destacada.

Agora temos o “alvo de queda” atual, sobre o qual estamos voando, na variável currentDroppable durante todo o processo e podemos usá-lo para destacar ou qualquer outra coisa.

Resumo

Nós consideramos um algoritmo Drag’n’Drop básico.

Os principais componentes:

  1. Fluxo de eventos: ball.mousedowndocument.mousemoveball.mouseup (não se esqueça de cancelar o nativo ondragstart).
  2. No início do arrasto – lembre-se do deslocamento inicial do ponteiro em relação ao elemento: shiftX/shiftY e mantenha-o durante o arrasto.
  3. Detecte elementos soltáveis e er o ponteiro usando document.elementFromPoint.

Podemos estabelecer muito nesta base.

  • On mouseup podemos finalizar intelectualmente a queda: alterar dados, mover elementos.
  • Podemos destacar os elementos sobre os quais estamos voando.
  • Nós pode limitar o arrasto em uma determinada área ou direção.
  • Podemos usar a delegação de eventos para mousedown/up. Um manipulador de eventos de grande área que verifica event.target pode gerenciar Drag’n’Drop para centenas de elementos.
  • E assim por diante.

Existem estruturas que constroem arquitetura sobre ele: DragZone, Droppable, Draggable e outras classes. A maioria deles faz coisas semelhantes ao descrito acima, então deve ser fácil de entendê-los agora. Ou faça o seu próprio, como você pode ver que é fácil de fazer, às vezes mais fácil do que adaptar uma solução de terceiros.

Write a Comment

O seu endereço de email não será publicado. Campos obrigatórios marcados com *