HTML Draggable - stop ghost snapping back to original location after drag

85 Views Asked by At

I'm trying to create a simple "drag and drop" to reorder items in a list. I have them updating the order, but there is an animation of the item drifting back to the original location when I stop dragging...

I have inserted preventDefault and stopPropagation all over the place, it doesn't seem to work.

If you run the snippet below, and drag the items to rearrange the order, watch the ghost drifting back when you let go of the mouse:

let cancelDefault = (e) => {
    e.preventDefault();
    e.stopPropagation();
    return false
  }

  let list = document.getElementById('song-list');
  let draggedElement = null;
  let draggedText = null;

  let createSongElement = (songData) => {
    let song = document.createElement('div');
    song.classList.add('draggable-song');
    song.setAttribute('draggable', true);
    song.textContent = songData[1];

    song.addEventListener("dragstart", (event) => {
      draggedElement = event.target;
      draggedText = draggedElement.textContent;
      draggedElement.classList.add("dragging");
    });
    song.addEventListener("dragend", (event) => {
      draggedElement.classList.remove("dragging");
      draggedElement = null;
      cancelDefault(event);
    });
    song.addEventListener("dragenter", (event) => { cancelDefault(event) });
    song.addEventListener("drop", (event) => { cancelDefault(event) });
    // song.addEventListener("drag", (event) => { draggedElement.updateValueFromPosition({ x: event.x, y: event.y }) });

    song.addEventListener("dragover", (event) => {
      if(event.target.classList.contains('draggable-song') && draggedElement.textContent != event.target.textContent) {
        songIndex = Array.prototype.indexOf.call(list.children, draggedElement);
        targetIndex = Array.prototype.indexOf.call(list.children, event.target);
        if(targetIndex > songIndex) {
          event.target.after(draggedElement);
        } else {
          event.target.before(draggedElement);
        }
      }
      cancelDefault(event);
    });

    return(song);
  }


  let songs = [
    ['1234', 'Garden on the kitchen floor'],
    ['1423', 'Never say never'],
    ['3251', 'Sitting, waiting, wishing'],
    ['412', 'Murder on the dance floor'],
    ['3251421', 'To the moon and back'],
  ];
  
  songs.forEach((songData) => {
    let song = createSongElement(songData);
    list.appendChild(song)
  })
.draggable-song {
  height: 26px;
}

.dragging {
  transition: 0.001s;
  transform: translateX(-9999px);
}
<div id='song-list' class='title-list'>

</div>

1

There are 1 best solutions below

0
Mirror318 On

Some properties (transitions and visibility included) force this snapback behavior, some don't (e.g. setting color: transparent):

.dragging {
    color: transparent;
}

If you want to hide the original element, but show the ghost, add the css class in a timeout (async) call, even with zero seconds:

let dragStart = (event) => {
  draggedElement = event.target;
  setTimeout(function(){ draggedElement.classList.add("dragging") }, 0);
}

Full snippet now working:

let cancelDefault = (e) => {
    e.preventDefault();
    e.stopPropagation();
    return false
  }

  let list = document.getElementById('song-list');
  let draggedElement = null;
  let draggedText = null;

  let createSongElement = (songData) => {
    let song = document.createElement('div');
    song.classList.add('draggable-song');
    song.setAttribute('draggable', true);
    song.textContent = songData[1];

    song.addEventListener("dragstart", (event) => {
      draggedElement = event.target;
      draggedText = draggedElement.textContent;
      setTimeout(function(){ draggedElement.classList.add("dragging") }, 0);
    });
    song.addEventListener("dragend", (event) => {
      draggedElement.classList.remove("dragging");
      draggedElement = null;
      cancelDefault(event);
    });
    song.addEventListener("dragenter", (event) => { cancelDefault(event) });
    song.addEventListener("drop", (event) => { cancelDefault(event) });
    // song.addEventListener("drag", (event) => { draggedElement.updateValueFromPosition({ x: event.x, y: event.y }) });

    song.addEventListener("dragover", (event) => {
      if(event.target.classList.contains('draggable-song') && draggedElement.textContent != event.target.textContent) {
        songIndex = Array.prototype.indexOf.call(list.children, draggedElement);
        targetIndex = Array.prototype.indexOf.call(list.children, event.target);
        if(targetIndex > songIndex) {
          event.target.after(draggedElement);
        } else {
          event.target.before(draggedElement);
        }
      }
      cancelDefault(event);
    });

    return(song);
  }


  let songs = [
    ['1234', 'Garden on the kitchen floor'],
    ['1423', 'Never say never'],
    ['3251', 'Sitting, waiting, wishing'],
    ['412', 'Murder on the dance floor'],
    ['3251421', 'To the moon and back'],
  ];
  
  songs.forEach((songData) => {
    let song = createSongElement(songData);
    list.appendChild(song)
  })
.draggable-song {
  height: 26px;
}

.dragging {
  color: transparent;
}
<div id='song-list' class='title-list'>

</div>