Поменять местами при перемещении элемента, как на панели задач Windows (чистый JavaScript)

#javascript #html #dom

#javascript #HTML #dom

Вопрос:

Я написал этот код, чтобы сделать любой элемент с классом draggable перетаскиваемым.

 const d = document.getElementsByClassName("draggable");

for (let i = 0; i < d.length; i  ) {
  d[i].style.position = "relative";
}

function filter(e) {
  let target = e.target;

  if (!target.classList.contains("draggable")) {
    return;
  }

  target.moving = true;
  
  e.clientX ?
  (target.oldX = e.clientX,
  target.oldY = e.clientY) :
  (target.oldX = e.touches[0].clientX,
  target.oldY = e.touches[0].clientY)

  target.oldLeft = window.getComputedStyle(target).getPropertyValue('left').split('px')[0] * 1;
  target.oldTop = window.getComputedStyle(target).getPropertyValue('top').split('px')[0] * 1;

  document.onmousemove = dr;
  document.addEventListener('touchmove', dr, {passive: false})

  function dr(event) {
    event.preventDefault();

    if (!target.moving) {
      return;
    }

    event.clientX ?
    (target.distX = event.clientX - target.oldX,
    target.distY = event.clientY - target.oldY) :
    (target.distX = event.touches[0].clientX - target.oldX,
    target.distY = event.touches[0].clientY - target.oldY)

    target.style.left = target.oldLeft   target.distX   "px";
    target.style.top = target.oldTop   target.distY   "px";
  }

  function endDrag() {
    target.moving = false;
  }
  target.onmouseup = endDrag;
  target.ontouchend = endDrag;
}
document.onmousedown = filter;
document.ontouchstart = filter;  
 div {
  width: 100px;
  height: 100px;
  background: red;
}  
 <div class="draggable"></div>  

tl; dr- Я хочу создать панель задач Windows, в которой элементы можно перемещать, а другие элементы перемещаться вправо или влево в зависимости от того, куда к нему приближается перетаскиваемый элемент.

Я хочу, чтобы перетаскиваемые элементы привязывались к сетке аналогично тому, что происходит, когда вы перетаскиваете значок на панели задач Windows или вкладку на другой вкладке в вашем браузере.

Ниже приведена моя попытка. Я убрал перемещение по вертикальной оси и поддержку касания, чтобы сделать код более читаемым. Привязка работает нормально, но элемент, на который наведен указатель мыши, не перемещается в другое пространство.

 const d = document.getElementsByClassName("draggable");

let grid = 50;

for (let i = 0; i < d.length; i  ) {
  d[i].style.position = "relative";
  d[i].onmousedown = filter;
}

function filter(e) {
  let target = e.target;

  target.moving = true;
  target.oldX = e.clientX;

  target.oldLeft = window.getComputedStyle(target).getPropertyValue('left').split('px')[0] * 1;

  document.onmousemove = dr;

  function dr(event) {
    event.preventDefault();

    if (!target.moving) {
      return;
    }

    target.distX = event.clientX - target.oldX;
    
    target.style.left = target.oldLeft   Math.round(target.distX / grid) * grid   'px'
  }

  function endDrag() {
    target.moving = false;
  }
  document.onmouseup = endDrag;
}  
 .parent {
  width: 100%;
  height: 100%;
  background: lime;
  display: flex;
  align-items: center;
}

.child {
  width: 50px;
  height: 50px;
  position: relative;
}

.one {
  background: red;
}

.two {
  background: blue;
}  
 <div class="parent">
  <div class="child one draggable"></div>
  <div class="child two draggable"></div>
</div>  

Кроме того, я думаю, что проверка того, когда мышь пересекла половину ширины div , во время перетаскивания элемента, div должна переместить либо одну единицу влево или вправо в зависимости от того, находится ли элемент слева или справа от перетаскиваемого элемента. С проверкой проблем нет. Мы можем просто сравнить величины элементов offsetLeft . Но как мне заставить элемент перемещаться?

Пожалуйста, попробуйте ответить на ванильном javascript.

Изменения: 1. Обновленный код 2. Обновленный заголовок 3. Обновлен tl; dr и изменен заголовок 3. Добавлено больше тегов

Комментарии:

1. На самом деле у вас есть расширение / фреймворк для этого, он называется sortableJS. Не такой уж большой (около 30 КБ). Почему бы вам не попробовать это? Вы даже можете перетаскивать из одного списка в другой, это действительно здорово. Дайте мне знать, если это поможет. Плюс это написано на чистом js (я думаю, не уверен)

2. Я видел расширение. Но, как уже упоминалось в вопросе, я хочу сделать это на чистом JavaScript. Хотя одна вещь, которую я узнал из расширения, заключается в том, что использование API перетаскивания HTML может быть более разумной идеей.

3. Зачем изобретать велосипед, человек? Конечно, вы можете перестроить javascript на C, но зачем? Если кто-то это сделал, почему бы не использовать это?

4. Как вы упомянули, библиотека превышает 30 КБ. Мне не нужны все его отдельные функции. Я уверен, что смогу написать код, который удовлетворяет вопросу размером менее 1-3 КБ. Я думаю, что я близок к завершению. В течение следующих 24 часов я опубликую ответ. Я надеюсь, что вы будете следить за развитием этого вопроса с большим интересом.

5. Я сделаю, потому что я хотел сделать что-то такое же, и у меня сдали нервы, хахаха, а затем я, наконец, просто остановился на расширении. Удачи!

Ответ №1:

Способ 1: Чистый JS-код, продолжающий вопрос

Следующий код соответствует требованию вопроса. Я добавил больше элементов в исходный код (больше элементов = больше удовольствия). Я также добавил комментарии в код. Я уверен, что вы поймете код, просто прочитав его. В любом случае, вот краткое объяснение.

Привязка target к сетке

Сначала нам нужно привязать элементы к сетке. Сначала мы делаем привязку target , смотрите следующий раздел для привязки других элементов. В строке указана ширина сетки в пикселях let grid = ... . Для плавной анимации мы хотим, чтобы target элемент щелкал при завершении перетаскивания, а не во время перетаскивания. Эта строка кода в функции endDrag привязывает target к сетке, когда перетаскивание завершено.

 target.style.left = target.oldLeft   Math.round(target.distX / grid) * grid   "px";
  

Перемещение других элементов на основе target

Нам также нужно переместить элемент, положение которого target занимает. В противном случае они бы перекрывались. Функция moveElementAt выполняет эту работу. Это то, что происходит в moveElementAt .

  • Мы называем любой элемент, который сталкивается с target верхним левым углом elementAt . Свойство JavaScript .elementFromPoint выполняет проверку.
  • При проверке мы исключаем target сам элемент, установив для его CSS pointer-events значение none . Мы ничего не делаем, если elementAt является родительским элементом.
  • Мы проверяем, приближается ли элемент elementAt слева или справа по некоторой математической логике.
    • Если target элемент поступает справа, он elementAt перемещает grid элементы вправо.
    • Если target элемент исходит слева, он elementAt перемещает grid элементы влево.

 const d = document.getElementsByClassName("draggable");

let grid = 50; //Width of one grid box

for (let i = 0; i < d.length; i  ) {
  d[i].style.position = "relative";
}

function filter(e) {
  let target = e.target;

  target.moving = true;
  target.oldX = e.clientX;

  target.oldLeft =
    window
    .getComputedStyle(target)
    .getPropertyValue("left")
    .split("px")[0] * 1; //Get left style as a number

  document.onmousemove = dr;

  function dr(event) {
    event.preventDefault();

    if (!target.moving) {
      return;
    }
    target.distX = event.clientX - target.oldX;
    target.style.left = target.oldLeft   target.distX   "px";
    target.style.pointerEvents = "none"; //Stops target from being elementAt
    moveElementAt();
  }

  function endDrag() {
    target.moving = false;
    target.style.left =
      target.oldLeft   Math.round(target.distX / grid) * grid   "px";
    moveElementAt(); //Do it at endDrag() also to stop elements from overlapping
    target.style.pointerEvents = "auto";
  }

  function moveElementAt() {
    let rootEl = target.parentNode;
    let elementAt = document.elementFromPoint(
      target.offsetLeft,
      target.offsetTop //Get element at target's coordinates
    );

    if (elementAt === rootEl) {
      return
    } //Stop rootEl from moving

    //Move elementAt either grid units left or right depending on which way target is approaching it from
    if (target.offsetLeft - elementAt.offsetLeft * 1 <= grid / 2) //Can also compare to 0, comparing to grid/2 stops elements' position from breaking when moving very fast to some extent
    {
      elementAt.style.left =
        window
        .getComputedStyle(elementAt)
        .getPropertyValue("left")
        .split("px")[0] * 1 - grid   "px";
    } else {
      elementAt.style.left =
        window
        .getComputedStyle(elementAt)
        .getPropertyValue("left")
        .split("px")[0] * 1   grid   "px";
    }

  }
  document.onmouseup = endDrag;
}
document.onmousedown = filter;  
 .parent {
  width: 100%;
  height: 100%;
  background: lime;
  display: flex;
  align-items: center;
}

.child {
  width: 50px;
  height: 50px;
  position: relative;
}

.one {
  background: red;
}

.two {
  background: blue;
}

.three {
  background: brown;
}

.four {
  background: pink;
}  
 <!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" />

  <title>Hello!</title>
  <link rel="stylesheet" href="/style.css" />
  <script src="/scriptmain.js"></script>
</head>

<body>
  <div class="parent" id="parent">
    <div class="child one draggable"></div>
    <div class="child two draggable"></div>
    <div class="child three draggable"></div>
    <div class="child four draggable"></div>
  </div>
</body>

</html>  

Код немного сбивается при необычно быстром перетаскивании. Я исправлю сбой позже, хотя.

Способ 2: Использование API перетаскивания / библиотеки

Джон Незбит уведомил меня, что существует библиотека под названием SortableJs, специально предназначенная для этой цели. Вопрос задан для решения на чистом JS. Итак, я закодировал метод, который использовал API перетаскивания. Вот фрагмент.

 function sortable(rootEl) {
  let dragEl;
  for (let i = 0; i < rootEl.children.length; i  ) {
    rootEl.children[i].draggable = true;
  }

  rootEl.ondragstart = evt => {
    dragEl = evt.target;
    rootEl.addEventListener("dragover", onDragOver);
    rootEl.addEventListener("dragend", onDragEnd);
  };

  function onDragOver(evt) {
    let target = evt.target;
    rootEl.insertBefore(
      dragEl,
      rootEl.children[0] === target ?
      rootEl.children[0] :
      target.nextSibling || target
    );
  }

  function onDragEnd(evt) {
    evt.preventDefault();

    rootEl.removeEventListener("dragover", onDragOver);
    rootEl.removeEventListener("dragend", onDragEnd);
  }
}
sortable(document.getElementById("parent"))  
 .parent {
  width: 100%;
  height: 100%;
  background: lime;
  display: flex;
  align-items: center;
}

.child {
  width: 50px;
  height: 50px;
  position: relative;
}

.one {
  background: red;
}

.two {
  background: blue;
}

.three {
  background: brown;
}

.four {
  background: pink;
}  
 <!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" />

  <title>Hello!</title>
  <link rel="stylesheet" href="/style.css" />
</head>

<body>
  <div class="parent" id="parent">
    <div class="child one draggable"></div>
    <div class="child two draggable"></div>
    <div class="child three draggable"></div>
    <div class="child four draggable"></div>
  </div>

  <script src="/script-dndmain.js"></script>
</body>

</html>  

Библиотека использует API перетаскивания HTML, который не дает мне желаемого результата. Но вы обязательно должны это проверить. Кроме того, ознакомьтесь с этой превосходной статьей от автора библиотеки, в которой объясняется (с использованием чистого js), как они создали эту библиотеку. Хотя я этим не пользовался, я уверен, что кому-то это поможет.