简介

easy-dnd是使用原生 Drag and Dropopen in new window api 封装的一套可以应用与复杂场景下的拖拽库,它屏蔽和解决了很多实际运用场景下原生api的一些问题,此库专注于web端,完全不适用于任何触摸设备,好处的显而易见的,easy-dnd的核心代码体积大约只有 9KB左右,gzip下只有3KB左右

easy-dnd不依赖与任何一个前端框架,由于不经过这些框架的运行时,因此可以直接用于浏览器环境中,性能得到了最佳保证,当然 easy-dnd 也提供了vuereact 的桥接方法,对于没有适配的前端框架,也可以极其容易的扩展

安装

可以直接使用任何一个你喜欢用的包管理工具安装easy-dnd

  • npm
$ npm i easy-dnd
  • pnpm
$ pnpm i easy-dnd
  • yarn
$ yarn add easy-dnd

原生拖拽api 快速上手

如果你已经会了原生的 Drag and Drop api,那么可以跳过这个章节,如果你还不会,可以直接查看MDN的官方文档open in new window,也可以通过以下的说明文档学习。当然你也可以不了解原生api的使用,直接查阅 easy-dnd 的使用说明

给dom元素添加拖拽特性

dom元素想要拖拽,只需要给dom绑定一个draggrable属性,就可以让这个dom添加上拖拽特性

<style>
  #box {
    width: 100px;
    height: 100px;
    background-color: red;
  }
</style>

<div id="box" draggable="true"></div>








 

拖拽事件

h5对于可拖拽的元素,提供了下面三个事件用于拖拽监听

  • dragstart: 拖拽开始
  • drag: 拖拽中
  • dragend: 拖拽结束
<div id="box" draggable="true"></div>

<script>
  
  box.addEventListener('dragstart', function(e){
    console.log('拖拽开始')
  })

  box.addEventListener('drag', function(e){
    console.log('拖拽中')
  })
  
  box.addEventListener('dragend', function(e){
    console.log('拖拽结束')
  })
  
</script>

拖拽过程事件

当有个可以拖拽的元素之后,元素在拖拽的过程中,会经过一些元素,这里给他取一个形象一点的名称:过程元素

image-20221001164233260

可以给这些过程元素绑定三个事件

  • dragenter: 拖拽元素进入该dom时触发
  • dragover: 拖拽元素在该dom范围内触发
  • dragleave: 拖拽元素离开该dom时触发
<div id="process">过程元素</div>

<script>
    
  process.addEventListener('dragenter', function(e){
    console.log('拖拽元素进入')
  })

  process.addEventListener('dragover', function(e){
    console.log('拖拽元素移动')
  })
  
  process.addEventListener('dragleave', function(e){
    console.log('拖拽元素离开')
  })
  
</script>

放置事件

当有可以拖拽的元素之后,总是需要有一个用于放置的地方,这个地方取一个形象的名称叫做目标元素。可以给目标元素绑定一个事件

  • drop: 拖拽元素松开的位置在目标元素中
<div id="target">目标元素</div>

<script>
    
  target.addEventListener('drop', function (e) {	
    console.log('松开')
  })
    
</script>

你可以尝试拖拽,并放置之后,会发现,这个函数根本没有被触发,这是因为还需要给目标元素再绑定一个事件 dragover,然后在这个函数中,执行阻止默认行为,表示允许拖拽元素放置在此元素中

<div id="target">目标元素</div>

<script>

  target.addEventListener('dragover', function (e) {	
	e.preventDefault()
  })
    
  target.addEventListener('drop', function (e) {	
    console.log('松开')
  })
    
</script>




 
 
 






这样,drop事件就会触发

原生拖拽api的问题

看过上方的文档,你一定会觉得,拖拽好像也不是很难,一个属性,7个事件。但是如果你真的直接使用上面的事件与api开发过复杂拖拽应用的话,一定会记忆尤新。因为不进行一系列”优化"的话,基本是不可用的。接下来就来仔细讲解一下,实际运用遇到的坑

drop元素存在多层HtmlElement

我们先看一个例子

<div class="drag-box" style="border: 1px double;width:100px; margin: 50px" draggable="true">
  dragItem
</div>

<div 
  class="drop1-box" 
  style="border: 1px solid;width:200px; height: 200px;margin: 50px;"
>
  drop1

  <div 
    class="drop2-box" 
    style="border: 1px solid;width:120px; height: 120px;margin: 30px;"
  >
    drop2
    <p
      class="drop3-box" 
      style="border: 1px solid;background: red;margin-top: 20px;height:40px"
    >
      drop3
    </p>
  </div>

</div>


<script>

  const drop1 = document.querySelector('.drop1-box')

  drop1.addEventListener('dragleave', (e) => {
    console.log('dragleave', e.target.className)
  })

  drop1.addEventListener('dragenter', (e) => {
    console.log('dragenter', e.target.className)
  })

</script>

drop-box绑定了dragenterdragover(这里不绑定了,与另外两个事件同理),dragleave事件

const dropBox = document.querySelector('.drop-box')

dropBox.addEventListener('dragenter', (e) => {
	console.log('dragenter', e.target.className)
})

dropBox.addEventListener('dragleave', (e) => {
	console.log('dragleave', e.target.className)
})

如果只在drop1drop2之间移动,那么一切事件触发表现性状与第一章节一样

注意,drop2drop3也在drop-box的范围内,按理来说,在drop2drop3范围内移动的时候,控制台依旧只会打印 drop-box的数据。但是,在具体操作过程中,表现形式如下

执行1操作的时候,控制台打印

dragenter drop1-box

执行2操作的时候,控制台打印

dragenter drop2-box
dragleave drop1-box

执行3操作的时候,控制台打印

dragenter drop3-box
dragleave drop2-box

执行4操作的时候,控制台打印

dragenter drop2-box
dragleave drop3-box

执行5操作的时候,控制台打印

dragenter drop1-box
dragleave drop2-box

执行6操作的时候,控制台打印

dragleave drop1-box

主要原因还需要了解一下,在日常开发中最常用的两种元素

  • Text节点:使用document.createTextNode创建的节点
  • HTMLElement节点:使用document.createElement创建的节点

如果所绑定的drag等相关子元素中,只存在Text,伪元素等节点,那么并不会存在上面问题,上面的问题只会出现在进入HtmlElement子节点中时出现

子节点也绑定了drop

在复杂应用中,不会只给最外层元素绑定drop等事件,还会给子节点,子孙节点等等无限嵌套节点绑定drop事件,这里先探讨两层的情况下的例子,无限嵌套其实同理。比如接着上一节的例子,给drop3绑定相关事件

const drop3 = document.querySelector('.drop3-box')
drop3.addEventListener('dragleave', (e) => {
    console.log('drop3 dragleave', e.target.className)
})

drop3.addEventListener('dragenter', (e) => {
    console.log('drop3 dragenter', e.target.className)
})

事件触发逻辑如下

执行1操作的时候,控制台打印

drop1 dragenter drop1-box

执行2操作的时候,控制台打印

drop1 dragenter drop2-box
drop1 dragleave drop1-box

执行3操作的时候,控制台打印

drop3 dragenter drop3-box
drop1 dragenter drop3-box
drop1 dragleave drop2-box

执行4操作的时候,控制台打印

drop1 dragenter drop2-box
drop3 dragleave drop3-box
drop1 dragleave drop3-box

执行5操作的时候,控制台打印

drop1 dragenter drop1-box
drop1 dragleave drop2-box

执行6操作的时候,控制台打印

drop1 dragleave drop1-box

自己既可以drag也可以drop

如果某一个元素自己又可以拖拽,又允许drop,那么就会出现自己的子节点可以是自己的情况。例子如下,当然这个只是简化模型,具体情况比这个复杂,比如祖先节点拖到十八代孙节点下

<div class="drag-box" style="border: 1px double;width:100px; margin: 100px" draggable="true">
  dragItem
</div>

<script>

  const drag = document.querySelector('.drag-box')

  drag.addEventListener('dragover', (e)=>e.preventDefault())

  drag.addEventListener('drop', (e)=>{
    console.log('drop')
  })

</script>
最近更新时间: