Как лучше сделать сортировальний драг - дроп?

Помогите создать драг дроп сортировальний, у меня получился только простой и то через пень колоду, если есть идея как сделать тень только по елементу а не всей доски подскажите как сделать, и помогите пожалуста сделать сортировальний драг дроп, буду очень боагодарен

const lists = document.querySelectorAll(".boards__list")

// ADD TASK
function addTask() {
  const addTaskBtn = document.querySelector(".boards__item--add")
  const form = document.querySelector(".boards__form")
  const addBtn = document.querySelector(".boards__buttons--add")
  const cancelBtn = document.querySelector(".boards__buttons--cancel")
  const area = document.querySelector(".boards__form--area")
  let areaValue

  form.addEventListener("submit", (e) => {
    e.preventDefault()
  })

  addTaskBtn.addEventListener("click", () => {
    form.style.display = "block"
    addTaskBtn.style.display = "none"
    addBtn.style.display = "none"

    area.addEventListener("input", (e) => {
      value = e.target.value

      if (value) {
        addBtn.style.display = "block"
      } else {
        addBtn.style.display = "none"
      }
    })
  })

  cancelBtn.addEventListener("click", () => {
    area.value = ""
    value = ""
    form.style.display = "none"
    addTaskBtn.style.display = "block"
  })

  addBtn.addEventListener("click", () => {
    let newItem = document.createElement("div")
    newItem.classList.add("boards__list--item")
    newItem.setAttribute("draggable", "true")
    newItem.textContent = value
    lists[0].append(newItem)

    form.style.display = "none"
    addTaskBtn.style.display = "block"
    area.value = ""
    value = ""

    dragNdrop()
  })
}

addTask()

// ADD BOARD
function addBoard() {
  const addBoard = document.querySelector(".addBoard")
  const boards = document.querySelector(".boards")

  addBoard.addEventListener("click", () => {
    const board = document.createElement("div")
    board.classList.add("boards__item")
    board.innerHTML = `
      <span class="boards__title" contenteditable="true"> Board's name </span>
      <div class="boards__list"></div>
    `
    boards.append(board)

    changeTitle()
    dragNdrop()
  })
}

addBoard()

// CHANGE TITLE
function changeTitle() {
  const titles = document.querySelectorAll(".boards__title")

  titles.forEach((item) => {
    item.addEventListener("click", (e) => {
      e.target.textContent = ""
    })
  })
}

changeTitle()

// DRAGNDROP
let dragItem = null

function dragNdrop() {
  const listItem = document.querySelectorAll(".boards__list--item")
  const lists = document.querySelectorAll(".boards__list")

  listItem.forEach((item) => {
    item.addEventListener("dragstart", function () {
      dragItem = this

      setTimeout(() => {
        item.style.display = "none"
      }, 0)
    })

    item.addEventListener("dragend", function () {
      dragItem = null

      setTimeout(() => {
        item.style.display = "block"
      }, 0)
    })
  })

  lists.forEach((item) => {
    item.addEventListener("dragover", function (e) {
      e.preventDefault()
    })

    item.addEventListener("dragenter", function (e) {
      e.preventDefault()

      this.style.backgroundColor = "#959595"
      this.style.borderRadius = "3px"
    })

    item.addEventListener("dragleave", function (e) {
      e.preventDefault()

      this.style.backgroundColor = "transparent"
    })

    item.addEventListener("drop", function (e) {
      e.preventDefault()
      this.style.backgroundColor = "transparent"
      this.appendChild(dragItem)
    })
  })
}

dragNdrop()
// VARIABLES
$body-color: #0079bf;
$card-color: #ebecf0;
$text-color: #3e4f6b;
$footer-text-color: #7f8a9d;
$danger-color: #de350b;
$white-color: #ffff;

/* MAIN STYLES */

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

html {
  overflow-x: hidden;
}

body {
  margin: 0;
  font-family: "Roboto", sans-serif;
  font-size: 15px;
  background-color: $body-color;
  color: $white-color;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
}

a {
  text-decoration: none;
}

/* CONTAINER */
.container {
  width: 100%;
  max-width: 1170px;
  margin: 0 auto;
  padding: 15px;
}

// BOARDS
.boards {
  display: flex;
  width: 100%;

  &__item {
    display: flex;
    flex-direction: column;
    align-self: baseline;
    max-width: 270px;
    width: 100%;
    padding: 10px;
    margin-left: 30px;

    border-radius: 3px;
    background-color: $card-color;
    color: $text-color;

    transition: all 0.2s ease;

    &:first-child {
      margin-left: 0;
    }

    &--add {
      margin-top: 5px;
      padding: 7px;
      cursor: pointer;

      border-radius: 3px;
      color: $footer-text-color;

      &:hover {
        background-color: #091e4214;
      }
    }
  }

  &__title {
    padding: 7px;

    font-size: 15px;
    font-weight: 500;

    &:focus {
      outline: 2px solid $body-color;
    }
  }

  &__list {
    // min-height: 35px;

    &--item {
      padding: 7px;
      margin: 5px 0;
      cursor: pointer;

      background-color: $white-color;
      border-radius: 3px;

      font-weight: 400;
    }
  }

  &__form {
    margin-top: auto;
    display: none;

    border-radius: 3px;

    &--area {
      width: 100%;
      padding: 7px;

      resize: none;
      outline: none;
      border: 0;
      border-radius: 3px;
      background-color: $white-color;
      color: $text-color;
    }
  }

  &__buttons {
    display: flex;

    &--add {
      padding: 10px 15px;

      background-color: $body-color;
      outline: none;
      border: 0;
      border-radius: 3px;
      color: $white-color;
      cursor: pointer;
    }

    &--cancel {
      padding: 10px 15px;
      margin-left: 5px;

      background-color: $danger-color;
      outline: none;
      border: 0;
      border-radius: 3px;
      color: $white-color;
      cursor: pointer;
    }
  }
}

.addBoard {
  width: 200px;
  padding: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  position: absolute;
  bottom: 50px;
  left: 50%;
  transform: translate(-50%);

  background-color: #ffffff3d;
  transition: 0.2s all ease;

  &:hover {
    background-color: #dedede;
  }
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Trello Clone</title>
    <!-- GOOGLE FONTS -->
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap"
      rel="stylesheet"
    />
    <!-- CSS -->
    <link rel="stylesheet" href="./assets/css/style.css" />
  </head>
  <body>
    <!-- container -->
    <div class="container">
      <!-- boards -->
      <div class="boards">
        <!-- boards__item -->
        <div class="boards__item">
          <span class="boards__title" contenteditable="true">First board</span>
          <!-- boards__list -->
          <div class="boards__list">
            <div class="boards__list--item" draggable="true">Starts card</div>
          </div>
          <!-- /boards__list -->

          <form class="boards__form">
            <textarea
              id="boards__form--area"
              class="boards__form--area"
              placeholder="Card's name"
            ></textarea>
            <div class="boards__buttons">
              <button class="boards__buttons--add">Add card</button>
              <button class="boards__buttons--cancel">Cancel</button>
            </div>
          </form>

          <div class="boards__item--add"><span>+</span> Add card</div>
        </div>
      </div>
      <!-- /boards -->

      <div class="addBoard"><span>+</span> Add more board</div>
    </div>
    <!-- /container -->

    <!-- JavaScript -->
    <script src="./assets/js/app.js"></script>
  </body>
</html>


Ответы (1 шт):

Автор решения: nazarpunk

Попробуйте так.

const list = document.querySelector('.list');
const listItems = document.querySelectorAll('.list-item');
const listHidden = document.querySelector('.list-hidden');

// let dragIndex, dragSource

const getMouseOffset = evt => {
  const targetRect = evt.target.getBoundingClientRect();
  const offset = {
    x: evt.pageX - targetRect.left,
    y: evt.pageY - targetRect.top
  };

  return offset;
};

const getElementVerticalCenter = el => {
  const rect = el.getBoundingClientRect();
  return (rect.bottom - rect.top) / 2;
};

const appendPlaceholder = (evt, idx) => {
  evt.preventDefault();
  if (idx === dragIndex) {
    return;
  }

  const offset = getMouseOffset(evt);
  const middleY = getElementVerticalCenter(evt.target);
  const placeholder = list.children[dragIndex];

  // console.log(`hover on ${idx} ${offset.y > middleY ? 'bottom half' : 'top half'}`)
  if (offset.y > middleY) {
    list.insertBefore(evt.target, placeholder);
  } else if (list.children[idx + 1]) {
    list.insertBefore(evt.target.nextSibling || evt.target, placeholder);
  }
  return;
};

function sortable(rootEl, onUpdate) {
  var dragEl;

  // Making all siblings movable
  [].slice.call(rootEl.children).forEach(function(itemEl) {
    itemEl.draggable = true;
  });

  // Function responsible for sorting
  function _onDragOver(evt) {
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'move';

    var target = evt.target;
    if (target && target !== dragEl && target.nodeName == 'DIV') {
      // Sorting
      const offset = getMouseOffset(evt);
      const middleY = getElementVerticalCenter(evt.target);

      if (offset.y > middleY) {
        rootEl.insertBefore(dragEl, target.nextSibling);
      } else {
        rootEl.insertBefore(dragEl, target);
      }
    }
  }

  // End of sorting
  function _onDragEnd(evt) {
    evt.preventDefault();

    dragEl.classList.remove('ghost');
    rootEl.removeEventListener('dragover', _onDragOver, false);
    rootEl.removeEventListener('dragend', _onDragEnd, false);


    // Notification about the end of sorting
    onUpdate(dragEl);
  }

  // Sorting starts
  rootEl.addEventListener('dragstart', function(evt) {
    dragEl = evt.target; // Remembering an element that will be moved

    // Limiting the movement type
    evt.dataTransfer.effectAllowed = 'move';
    evt.dataTransfer.setData('Text', dragEl.textContent);


    // Subscribing to the events at dnd
    rootEl.addEventListener('dragover', _onDragOver, false);
    rootEl.addEventListener('dragend', _onDragEnd, false);


    setTimeout(function() {
      // If this action is performed without setTimeout, then
      // the moved object will be of this class.
      dragEl.classList.add('ghost');
    }, 0);
  }, false);
}

// Using                    
sortable(list, function(item) {
  console.log(item);
});
.list {
  background: #4286f4;
  width: 500px;
  padding: 1rem;
}

.list-hidden {
  position: absolute;
  top: -9999px;
  left: -9999px;
}

.ghost {
  opacity: .4;
}

.list-item {
  background: #8bb3f4;
  padding: 0.5rem;
  border: 1px solid white;
  margin: 1px 0;
}

.placeholder {
  display: none;
  background: #303742;
  pointer-events: none;
}
<div class='list-hidden'></div>
<div class="list">
  <div class="list-item" draggable=true>
    Item 1
  </div>
  <div class="list-item" draggable=true>
    Item 2
  </div>
  <div class="list-item" draggable=true>
    Item 3
  </div>
  <div class="list-item" draggable=true>
    Item 4
  </div>
</div>

→ Ссылка