javascripthtmlcssdrag-and-dropevent-handling

Smooth style application on target element in Dragenter and Dragleave


I'd like to apply a dashed border around my targeted table columns when a draggable element is hovered over the column on dragenter, and remove the border on dragleave.

I am able to successfully target and style these table columns, however there is a "blinking" effect where the border gets quickly applied/removed. Is there a way I can add in a specific check to make this drag transition between columns smoother and avoid these event listeners/style application and removal from being executed unnecessarily?

<div draggable="true">Drop item</div>

<table>
  <tbody>
    <tr>
      <td class="cell" data-column-number="1">Cell-1</td>
      <td class="cell" data-column-number="2">Cell-2</td>
    </tr>
    <tr>
      <td class="cell" data-column-number="1">Cell-3</td>
      <td class="cell" data-column-number="2">Cell-4</td>
    </tr>
  </tbody>
</table>
const tds = document.querySelectorAll("[data-column-number]")

for (let td of tds) {
  td.addEventListener("dragenter", (event) => {
    let colNum = td.dataset.columnNumber
    let cols = document.querySelectorAll(`[data-column-number="${colNum}"]`)
    
    console.log("<-- drag enter --->")

    for (let col of cols) {
      col.classList.add("hovered")
    }
  })

  td.addEventListener("dragleave", (event) => {
    let colNum = td.dataset.columnNumber
    let cols = document.querySelectorAll(`[data-column-number="${colNum}"]`)
    
    console.log("<-- drag leave --->")

    for (let col of cols) {
      col.classList.remove("hovered")
    }
  })
}
div {
  border: dotted 2px green;
  width: 70px;
  background-color: yellow;
  text-align: center;
  cursor: grab;
}

div:active {
  cursor: grabbing;
}

td.hovered {
  border: dashed 2px red;
}

table {
  margin-top: 30px;
}

JsFiddle Link: https://jsfiddle.net/z8mkdj3y/26/


Solution

  • You are listening on each cell, not the actual "column", which gives a brief blinking when you move between cells. You should instead listen on the table itself and decide if the drag is still on the same column or is it on the other col/out of table:

    const table = document.querySelector('table')
    let currentCol = 0
    
    table.addEventListener('dragover', (e) => {
      const td = document.elementFromPoint(e.clientX, e.clientY)?.closest('td[data-column-number]')
      const newCol = td?.dataset.columnNumber
    
      if (!newCol || newCol == currentCol) return
      if (currentCol) {
        document.querySelectorAll(`[data-column-number='${currentCol}']`)
          .forEach(cell => cell.classList.remove("hovered"))
      }
      document.querySelectorAll(`[data-column-number='${newCol}']`)
        .forEach(cell => cell.classList.add('hovered'))
    
      currentCol = newCol
    });
    
    table.addEventListener('dragleave', (e) => {
      if (e.relatedTarget && table.contains(e.relatedTarget)) return
      if (currentCol) {
        document.querySelectorAll(`[data-column-number='${currentCol}']`)
          .forEach(cell => cell.classList.remove('hovered'))
        currentCol = 0
      }
    });
    div {
      border: dotted 2px green;
      width: 70px;
      background-color: yellow;
      text-align: center;
      cursor: grab;
    }
    
    div:active {
      cursor: grabbing;
    }
    
    td {
      border: solid 2px transparent
    }
    
    td.hovered {
      border: dashed 2px red;
    }
    
    table {
      margin-top: 30px;
    }
    <div draggable="true">Drop item</div>
    
    <table>
      <tbody>
        <tr>
          <td class="cell" data-column-number="1">Cell-1</td>
          <td class="cell" data-column-number="2">Cell-2</td>
        </tr>
        <tr>
          <td class="cell" data-column-number="1">Cell-3</td>
          <td class="cell" data-column-number="2">Cell-4</td>
        </tr>
      </tbody>
    </table>