聊聊原生拖拽API

159 阅读3分钟

拖拽api是我们前端非常常见的api了,比如拖拽排序,拖拽上传文件,树形结构的生成等等都会用到拖拽api,拖拽api本身不复杂难得是在应用上,下面用一个例子来简单使用下这些api

先实现布局如下

image.png

第一步:我们需要把左侧的元素变成可拖拽的,我们可以使用html属性给元素加一个draggable属性把它设为true,该元素就可以拖拽了

<div class="container">
  <ul class="drop-left">
    <li draggable="true">vue</li>
    <li draggable="true">react</li>
    <li draggable="true">webpack</li>
    <li draggable="true">node</li>
    <li draggable="true">js</li>
  </ul>
</div>

image.png

html的属性只是把它变成可拖拽元素,此时只能拖拽并没有效果,所以我们的通过书写js代码自定义一系列拖拽事件,拖拽其实就是一系列事件的组合

第二步:书写js代码监控元素拖拽和拖拽事件,我们可以通过js的事件委托直接监控他们的父元素来实现监控这个区域内所有和拖拽相关的事件了

const container = document.querySelector(".container");

// 此事件表示拖拽开始事件,可通过e.target拿到拖拽的元素
container.ondragstart = (e) => {
  console.log("start", e.target);
};

// 拖拽经过哪些元素上面就会触发该事件,可通过e.target知道我拖拽的元素目前在那个元素上面
container.ondragover = (e) => {
  console.log("over", e.target);
};

// 可以通过这个事件拿到当前元素拖到了哪个元素之上
container.ondragenter = (e) => {
  console.log("enter", e.target);
};

// 通过这个事件可以知道当前拖拽的元素是在那个元素落下的
container.ondrop = (e) => {
  console.log("drop", e.target);
};

注意:像div,td,li,table等元素都是不允许别的东西元素拖拽到他上边的,所以不会触发drop事件,这是浏览器的默认行为,所以我们的可以在over事件中阻止默认行为

container.ondragover = (e) => {
  e.preventDefault();
};

至此拖拽的事件已经写完了

第三步:修改拖拽元素默认自带的加号

image.png

我们可以通过e.dataTransfer.effectAllowed修改拖拽元素的状态

container.ondragstart = (e) => {
  e.dataTransfer.effectAllowed = "move";
};

image.png

但是我们的动态设置状态,比如说从左边拖到右侧是copy,从右侧拖动到左侧是move,所以我们还是可以通过自定义属性来控制,如下我么后续可通过data-dropstatus来获取状态。

<div class="container">
  <ul data-dropstatus="move" class="drop-left">
    <li data-effect="copy" draggable="true">vue</li>
    <li data-effect="copy" draggable="true">react</li>
    <li data-effect="copy" draggable="true">webpack</li>
    <li data-effect="copy" draggable="true">node</li>
    <li data-effect="copy" draggable="true">js</li>
  </ul>
  <div class="drop-container">
    <div data-dropstatus="copy"></div>
    <div data-dropstatus="copy"></div>
    <div data-dropstatus="copy"></div>
    <div data-dropstatus="copy"></div>
  </div>
</div>

container.ondragstart = (e) => {
  e.dataTransfer.effectAllowed = e.target.dataset.effect;
};

第四步:改变右侧容器当左侧拖进来时的背景色

container.ondragenter = (e) => {
  e.target.classList.add("hover-background");
};

但这么些会有问题,enter事件不仅会在子元素出发也会在父元素出发,所以整个container的背景色都会变。所以我们的修改为哪个元素背景色变是取决于我当前元素能拖拽到那个地方,能拖拽的地方背景色变不能拖拽的地方不能变,所以修改为如下

function getDropNode(node) {
  while (node) {
    if (node.dataset?.dropstatus) {
      return node;
    }
    node = node.parentNode;
  }
}
​
container.ondragenter = (e) => {
  const dropNode = getDropNode(e.target);
  if (
    dropNode &&
    dropNode.dataset.dropstatus === e.dataTransfer.effectAllowed
  ) {
    e.target.classList.add("hover-background");
  }
};

此时会有个小问题就是经过的地方背景色都会变所以的清除下背景色

image.png

function clearClass() {
  document.querySelectorAll(".hover-background").forEach((item) => {
    item.classList.remove("hover-background");
  });
}

container.ondragenter = (e) => {
  clearClass();
  const dropNode = getDropNode(e.target);
  if (
    dropNode &&
    dropNode.dataset.dropstatus === e.dataTransfer.effectAllowed
  ) {
    e.target.classList.add("hover-background");
  }
};

第五步:处理drop放下事件 首先判断是复制元素还是删除元素,如果是是copy那么直接使用start事件中拖动的原始元素

let source;

container.ondragstart = (e) => {
  e.dataTransfer.effectAllowed = e.target.dataset.effect;
  source = e.target;
};

container.ondrop = (e) => {
  clearClass();
  const dropNode = getDropNode(e.target);
  if (
    dropNode &&
    dropNode.dataset.dropstatus === e.dataTransfer.effectAllowed
  ) {
    if (dropNode.dataset.dropstatus === "copy") {
      dropNode.innerHTML = "";
      const cloned = source.cloneNode(true);
      cloned.dataset.effect = "move";
      dropNode.appendChild(cloned);
    }
  }
};

如果是删除元素则直接remove就可

container.ondrop = (e) => {
  clearClass();
  const dropNode = getDropNode(e.target);
  if (
    dropNode &&
    dropNode.dataset.dropstatus === e.dataTransfer.effectAllowed
  ) {
    if (dropNode.dataset.dropstatus === "copy") {
      dropNode.innerHTML = "";
      const cloned = source.cloneNode(true);
      cloned.dataset.effect = "move";
      dropNode.appendChild(cloned);
    } else {
      source.remove();
    }
  }
};

以上就是一个拖拽demo的实现,虽然拖拽能做的功能很多但也都是基于这几个api事件实现,我们可以在里面扩展各种别的功能,像拖拽布局,上下拖拽,自定义布局等等功能。

结尾

随着现在技术的不断发展,用户对页面的要求也越来越高了,这时候就衍生出了自定义布局页面内容和组件,我们的Django-Vue-Admin刚好实现了自定义首页功能,欢迎过来一起交流和探讨

image.png

官网链接: Django-Vue-Admin

QQ群二维码,欢迎进群交流

image.png