像在 Windows 任务栏(纯 JavaScript)中一样在元素上移动时交换位置

mma*_*mma 8 html javascript 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;
Run Code Online (Sandbox Code Playgroud)
div {
  width: 100px;
  height: 100px;
  background: red;
}
Run Code Online (Sandbox Code Playgroud)
<div class="draggable"></div>
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)
.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;
}
Run Code Online (Sandbox Code Playgroud)
<div class="parent">
  <div class="child one draggable"></div>
  <div class="child two draggable"></div>
</div>
Run Code Online (Sandbox Code Playgroud)

此外,我认为检查鼠标何时跨过 a 宽度的一半div,当一个元素被拖动时,div应该向左或向右移动一个单位,具体取决于该元素是在被拖动元素的左侧还是右侧。检查部分没有问题。我们可以只比较元素' 的大小offsetLeft。但是如何让元素移动呢?

请尝试用香草 javascript 回答。

编辑: 1. 更新代码 2. 更新标题 3. 更新 tl; dr 并更改标题 3. 添加更多标签

mma*_*mma 1

方法1:纯JS代码继续问题

下面的代码可以满足题主的要求。我在原始代码中添加了更多元素(更多元素=更有趣)。我还在代码中添加了注释。我相信您只需阅读代码就会理解它。无论如何,这里有一个简短的解释。

捕捉target网格

我们首先需要捕捉网格中的元素。我们首先捕捉target,请参阅下一节来捕捉其他元素。网格的宽度(以像素为单位)在 line 中指定let grid = ...。为了实现平滑的动画,我们希望target在结束拖动时捕捉,而不是在拖动时捕捉。函数中的这行代码在拖动结束时endDrag将其捕捉到网格中。target

target.style.left = target.oldLeft + Math.round(target.distX / grid) * grid + "px";
Run Code Online (Sandbox Code Playgroud)

移动其他元素基于target

我们还需要移动其位置所在的元素target。否则,它们就会重叠。该函数moveElementAt完成这项工作。这就是发生在moveElementAt.

  • target我们命名与的左上角发生碰撞的任何元素elementAt。JavaScript 属性.elementFromPoint进行检查。
  • 在检查中,我们target通过将其 CSS 设置pointer-eventsnone. elementAt如果是父元素,我们什么都不做。
  • 我们通过一些数学逻辑检查元素是否elementAt从左或右接近 at 。
    • 如果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;
Run Code Online (Sandbox Code Playgroud)
.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;
}
Run Code Online (Sandbox Code Playgroud)
<!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>
Run Code Online (Sandbox Code Playgroud)

当拖动速度异常快时,代码有点问题。不过我稍后会修复这个故障。

方法 2:使用拖放 API/库

Jon Nezbit 通知我有一个名为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"))
Run Code Online (Sandbox Code Playgroud)
.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;
}
Run Code Online (Sandbox Code Playgroud)
<!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>
Run Code Online (Sandbox Code Playgroud)

该库使用 HTML 拖放 API,但它没有给出我想要的结果。但你绝对应该检查一下。另外,请查看该库作者的这篇精彩文章,其中解释了(使用纯 js)他们如何制作该库。虽然我没有使用它,但我相信有人会得到帮助。