技巧-拖拽元素排序

134 阅读3分钟

页面有几个元素按顺序纵向排列,希望能够拖拽这些元素进行排列。

image.png

1.添加拖拽属性

<div class="tables">
    <div draggable="true" class="item">1</div>
    <div draggable="true" class="item">2</div>
    <div draggable="true" class="item">3</div>
    <div draggable="true" class="item">4</div>
    <div draggable="true" class="item">5</div>
    <div draggable="true" class="item">6</div>
  </div>

这时候元素可以拖拽了,即跟着鼠标光标移动,但该元素的原来位置没有变。先处理样式,可以给元素添加一个新的 样式moving ,让其原来的位置看起来不一样。在元素被拖拽的时候,添加 moving类。

 .item {
    width: 400px;
    height: 45px;
    padding-left: 10px;
    line-height: 45px;
    background-color: pink;
    border-radius: 5px;
    margin-bottom: 20px;
    cursor: pointer;
    &.moving { //被拖拽时的样式
      background: transparent;
      color: transparent;
      border: 1px dashed #ccc;
    }
  }

这时候使用事件代理监听拖拽事件。

const onTrag = () => {
  const list = document.querySelector(".tables");

  // 事件委托 开始拖拽时
  list.ondragstart = (e) => {
    e.target.classList.add("moving"); //改变元素样式,但此时拖拽的元素 和 原来的位置 的样式都变了
  };
};

因为是刚拖拽的时候就添加样式,所以两个地方的样式都给改了。这时可放到一个异步环境里,拖拽开始的时候,跟随鼠标的样式不变。

 list.ondragstart = (e) => {
    setTimeout(() => {
      e.target.classList.add("moving"); 
    }, 0);
  };

2. 排序

拖拽元素后,需要放到某个位置,所以需要监听鼠标进入了哪个元素。这里要排除一些特殊情况,进入父元素和进入本身时,不做任何处理。

const onTrag = () => {
  const list = document.querySelector(".tables");
  let sourceNode; //闭包 记录拖拽的元素

  // 事件委托 开始拖拽时
  list.ondragstart = (e) => {
    setTimeout(() => {
      e.target.classList.add("moving");
    }, 0);
    sourceNode = e.target;
  };

  // 拖拽进入
  list.ondragenter = (e) => {
    if (e.target === list || e.target === sourceNode) return;//不做处理
  };
};

拖动元素的时候,有两个方向,向上和向下,那目标位置的元素就要做出相反的移动。现在可以通过获取 被拖动元素 和 进入位置元素 的下标来判断移动方向。

// 拖拽进入
  list.ondragenter = (e) => {
    if (e.target === list || e.target === sourceNode) return;

    const children = [...list.children];
    const sourceNodeIndex = children.indexOf(sourceNode);
    const targetNodeIndex = children.indexOf(e.target);
    if (sourceNodeIndex < targetNodeIndex) {
      list.insertBefore(sourceNode, e.target.nextElementSibling); //加入到 目标元素 的下一个元素之前
    } else {
      list.insertBefore(sourceNode, e.target);//加入到 目标元素之前
    }
  };

这时候,已经是可以拖动元素,且目标元素会移动到被拖拽元素的位置了,比如拖拽元素1元素2,那元素2就跑到元素1原来的位置了。此时也发现了,当松开的时候,元素1跑回去了,并没有进入元素2原来的位置。 其实,在默认情况下,所有的元素都都不允许别的东西拖动到自身,这是浏览器的默认行为。所以拖拽结束之后,它就是从哪来回哪去。那么解决这个问题的话,只需要阻止浏览器的默认行为。

 //拖拽结束
  list.ondragover = (e) => {
    e.preventDefault();
  };

最后,把目标位置元素的样式改回去就可以了。

 //拖拽结束后
  list.ondragend = (e) => {
    e.target.classList.remove("moving");
  };

完整代码:

const onTrag = () => {
  const list = document.querySelector(".tables");
  let sourceNode; //闭包 记录拖拽的元素

  // 事件委托 开始拖拽时
  list.ondragstart = (e) => {
    setTimeout(() => {
      e.target.classList.add("moving");
    }, 0);

    e.dataTransfer.effectAllowed = "move"; //解决拖拽过程中 鼠标光标变成一个 ➕ 的问题
    sourceNode = e.target;
  };

  // 拖拽进入
  list.ondragenter = (e) => {
    e.preventDefault();

    if (e.target === list || e.target === sourceNode) return;

    const children = [...list.children];
    const sourceNodeIndex = children.indexOf(sourceNode);
    const targetNodeIndex = children.indexOf(e.target);
    if (sourceNodeIndex < targetNodeIndex) {
      list.insertBefore(sourceNode, e.target.nextElementSibling); //加入到 目标元素 的下一个元素之前
    } else {
      list.insertBefore(sourceNode, e.target); //加入到 目标元素之前
    }
  };

  //拖拽结束
  list.ondragover = (e) => {
    e.preventDefault();
  };

  //拖拽结束后
  list.ondragend = (e) => {
    e.target.classList.remove("moving");
  };
};