css / html5 :hover state remains after drag and drop
Asked Answered
M

2

8

I'm using html5's drag and drop functionalities to rearrange dom elements on screen - I attach css behavior to the various states of dragging and dropping when I do this, but the problem I'm experiencing is that the hover state remains even after I've dragged, dropped, and moused out of a DOM element. Here's my code:

JAVASCRIPT:

function addDragListeners(){
    $('.segmentListItem').each(function(index){
        $(this)[0].addEventListener('dragstart',handleDragStart,false); //rollover for current
        $(this)[0].addEventListener('drop',handleDrop,false); //drops dragged element
        $(this)[0].addEventListener('dragover',handleDragOver,false); //allows us to drop
        $(this)[0].addEventListener('dragenter',handleDragEnter,false); //rollover for target
        $(this)[0].addEventListener('dragleave',handleDragLeave,false); //sets dragged item back to normal
        $(this)[0].addEventListener('dragend',handleDragEnd,false); //sets all back to normal
    });
}

function handleDragEnter(e) {
        // this / e.target is the current hover target.
        this.classList.add('over');
}

function handleDragLeave(e) {
        this.classList.remove('over');  // this / e.target is previous target element.
}

function handleDragEnd(e){
    $('.segmentListItem').removeClass('over'); //removes the over class on all elements
}

function handleDragStart(e){
    draggedItem = this;
    e.dataTransfer.effectAllowed = 'move';
}

function handleDragOver(e) {
    if (e.preventDefault) {
        e.preventDefault(); // Necessary. Allows us to drop.
    }
    e.dataTransfer.dropEffect = 'move';  // See the section on the DataTransfer object.
    return false;
}

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

    if (draggedItem != this) { //MH - swap if we're not dragging the item onto itself

        var draggedIndex = $('.segmentListItem').index($(draggedItem));
        var targetIndex = $('.segmentListItem').index($(this));

        if (draggedIndex > targetIndex){
            $(draggedItem).insertBefore($(this));
        } else {
            $(draggedItem).insertAfter($(this));
        }
    } 

    return false;
}

CSS:

a { border-radius: 10px; }
a:hover { background: #ccc; }
.segmentListItem { text-align:center; width: 50px; margin-right: 5px; font-size: 16px; display:inline-block; cursor:move; padding:10px; background: #fff; user-select: none; }
.segmentListItem.over { background: #000; color: #fff; }
Maurilia answered 16/8, 2012 at 14:24 Comment(4)
Did you ever figure this out? Experiencing the thing, and more annoying, when reordering elements via drag & drop, it applies the hover state to the element the moves into the place of the one being reordered.Tunny
It might be worth creating a JSFiddle, always helps :)Stiff
have you tried unbinding the event ? $(this).unbind('mouseenter mouseleave')Execrative
Can you provide a JSFiddle for this or your HTML?Scruff
A
2

Status (six years later)

According to https://bugs.webkit.org/show_bug.cgi?id=134555 this used to be a bug. However, it must have been fixed in the meantime, as it cannot be reproduce anymore in modern browsers. The only browser where I could still replicate it was IE11.

Working fix

You can replace the CSS :hover with a .hover class toggled from JS, in order to better control the hover state:

document.querySelectorAll('.segmentListItem a').forEach(function (item) {
  item.addEventListener('mouseenter', function () {
    this.classList.add('hover');
  });
  item.addEventListener('mouseleave', function () {
    this.classList.remove('hover');
  });
});

Code snippet below:

function addDragListeners() {
  $(".segmentListItem").each(function(index) {
    $(this)[0].addEventListener("dragstart", handleDragStart, false); //rollover for current
    $(this)[0].addEventListener("drop", handleDrop, false); //drops dragged element
    $(this)[0].addEventListener("dragover", handleDragOver, false); //allows us to drop
    $(this)[0].addEventListener("dragenter", handleDragEnter, false); //rollover for target
    $(this)[0].addEventListener("dragleave", handleDragLeave, false); //sets dragged item back to normal
    $(this)[0].addEventListener("dragend", handleDragEnd, false); //sets all back to normal
  });
}

function handleDragEnter(e) {
  // this / e.target is the current hover target.
  this.classList.add("over");
}

function handleDragLeave(e) {
  this.classList.remove("over"); // this / e.target is previous target element.
}

function handleDragEnd(e) {
  e.preventDefault();
  $(".segmentListItem").removeClass("over"); //removes the over class on all elements
}

function handleDragStart(e) {
  draggedItem = this;
  e.dataTransfer.effectAllowed = "move";
}

function handleDragOver(e) {
  if (e.preventDefault) {
    e.preventDefault(); // Necessary. Allows us to drop.
  }
  e.dataTransfer.dropEffect = "move"; // See the section on the DataTransfer object.
  return false;
}

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

  if (draggedItem != this) {
    //MH - swap if we're not dragging the item onto itself

    var draggedIndex = $(".segmentListItem").index($(draggedItem));
    var targetIndex = $(".segmentListItem").index($(this));

    if (draggedIndex > targetIndex) {
      $(draggedItem).insertBefore($(this));
    } else {
      $(draggedItem).insertAfter($(this));
    }
  }

  return false;
}

// JS fix starts here:
document.querySelectorAll('.segmentListItem a').forEach(function(item, idx) {
  item.addEventListener('mouseenter', function() {
    this.classList.add('hover');
  });
  item.addEventListener('mouseleave', function() {
    this.classList.remove('hover');
  });
});
// and ends here. Comment these lines, and uncomment the `a:hover` rule in CSS in order to see the initial code

addDragListeners();
a {
  border-radius: 10px;
}


/* a:hover {
  background: #ccc;
} */

.segmentListItem {
  text-align: center;
  width: 50px;
  margin-right: 5px;
  font-size: 16px;
  display: inline-block;
  cursor: move;
  padding: 10px;
  background: #fff;
  user-select: none;
}

.segmentListItem.over {
  background: #000;
  color: #fff;
}

.hover {
  background: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

<ul>
  <li class="segmentListItem">
    <a href="#">test1</a>
  </li>
  <li class="segmentListItem">
    <a href="#">test2</a>
  </li>
  <li class="segmentListItem">
    <a href="#">test3</a>
  </li>
</ul>
Acotyledon answered 3/4, 2019 at 12:40 Comment(4)
I can still repro the issue in Chrome v73 in year 2019.Wedge
Two more years later, this still reproduces in Chrome 88Zahn
Ironically, still have this issue in 2022Jedthus
2023 and still counting (has anyone found a fix?)Rossierossing
T
0

I was able to make it work creating a manual .hover class, adding it with mouseover, mouseleave events, and also adding a justDropped variable to state. When the drop starts, that variable sets to on, and on the mouseover function, I validate whether it's on or off, to block it from adding the class.

About 500ms after the drop finishes, with setTimeout I set justDropped to false again, that way allowing new interactions to trigger the hover state class.

Teofilateosinte answered 15/5 at 15:38 Comment(1)
Hi mheavers, Your answer could be improved with additional supporting information. Namely, you could add a sample of the code you mention that worked. This will help other users much better and will also help you gather more up-votes and with it increased reputation in the community.Freewheeling

© 2022 - 2024 — McMap. All rights reserved.