前言

拖动api是HTML5添加的API,提供了元素拖动的原生支持

拖动API相对于其他的事件API复杂了一些,原因就在于拖动是被拖动元素与被放置元素的互动涉及到了两个元素,而不是一个导致流程的复杂,但是其中的很多设计是有规律可循的.

现在我们来看一个基本的使用场景,这里有两个元素A和B,A是可拖动元素而B是可放置元素,我们将A放置到B中

  1. 鼠标按下后拖动元素A,触发A的dragstart事件
  2. 开始移动A元素
  3. 刚刚触碰到B容器,触发B的dragenter事件
  4. 移动到B上方
  5. 触发B容器的dragover事件,该事件类似于mouseover事件是连续触发的
  6. 松开鼠标放置A
  7. 触发B容器的drop事件
  8. 触发A元素的dragend事件

基本流程

我们来实现上面的几个步骤

html

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


<div id="display">
    目标区域
</div>

注意:被拖动的元素需要设置draggable属性

css

html,
body {
    height: 100%;
}

html,
body,
div {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

#test {
    margin: 0 auto;
    margin-top: 10%;
    width: 50%;
    height: 50%;
    background: #4CB8C4;
    background: linear-gradient(to right, #3CD3AD, #4CB8C4);
    box-shadow: 0 0 15px -3px black;
}

#display {
    margin: 0 auto;
    margin-top: 10%;
    width: 30%;
    height: 30%;
    background-color: rgba(0, 0, 0, 0.055);
}

javascript

let dargElem = document.getElementById('test');
let targetElem = document.getElementById('display');

dargElem.addEventListener('dragstart', (event) => {

    // 被拖动元素开始事件
    console.log(event);
})

// 拖动结束事件
dargElem.addEventListener('dragend', (event) => {

    console.log('拖动结束事件');
})

// 进入容器的事件
targetElem.addEventListener('dragenter', (event) => {

    event.preventDefault();
    console.log('拖动进入事件');
})

// 悬浮到容器上方的事件
targetElem.addEventListener('dragover', (event) => {
    
    event.preventDefault();
})

// 拖动事件
targetElem.addEventListener('drop', (event) => {

    console.log('触发放置事件')
})

这个例子中dargElem保存的是被拖动元素,我们为他注册了dragstartdragend事件,
另外一个变量targetElem是容器元素我们为他注册了dragenter dragover drop 事件

控制台输出:

开始拖动
拖动进入事件
触发放置事件
拖动结束事件

注意:这个例子中使用了event.preventDefault()用于阻止默认的行为,例如拖动a标签的时候有可能触发默认的跳转行为

所有事件列表(不包含厂商事件和浏览器不支持事件):

  • 被拖动的对象

    • dragstart 拖动开始触发
    • drag 拖动过程中触发 连续触发
    • dragend 拖动结束后触发
  • 被当作容器的元素

    • dragenter 元素进入时候触发
    • dragover 可拖动元素置于容器元素上方时触发 连续触发
    • drop 可拖动元素置入到容器元素中触发
    • dragleave 可拖动元素移动出容器元素时候触发

注意:容器元素一定需要监听dragover事件并且一开始调用event.preventDefault()后续的drop才会触发dragover相当于是一个过滤器,只允许指定的元素可以触发drop事件

数据的交互

前面说道拖动事件是两个元素的互动,但是仅仅使用事件是不够的,我们需要其他的手段在两个元素之间传递信息

dragstart事件触发的时候所提供的event对象中含有一个DataTransfer对象,这个对象允许在多个事件中保存信息且在所有拖动事件中存在

简单信息传递操作:

  1. 被拖动元素调用该对象的setData()方法设置值
  2. 容器元素在drop事件中使用event对象的DataTransfer属性的getData()方法获取在dragstart事件中设置的值

例子:

let dargElem = document.getElementById('test');
let targetElem = document.getElementById('display');

dargElem.addEventListener('dragstart', (event) => {

    event.dataTransfer.setData('test',[1,2,3]);
})

// 必须设置
targetElem.addEventListener('dragover', (event) => {
    event.preventDefault();
})

// 拖动事件
targetElem.addEventListener('drop', (event) => {

    event.preventDefault();
    console.log(event.dataTransfer.getData('test'));
})

可以看到DataTransfer数据的设置就是典型的键值操作而已

综合实例一个来自MDN的例子:

html

<div class="dropzone">
    <div id="draggable" draggable="true" ondragstart="event.dataTransfer.setData('text/plain',null)">
        This div is draggable
    </div>
</div>
<div class="dropzone"></div>
<div class="dropzone"></div>
<div class="dropzone"></div>

css

#draggable {
width: 200px;
height: 20px;
text-align: center;
background: white;
}

.dropzone {
width: 200px;
height: 20px;
background: blueviolet;
margin-bottom: 10px;
padding: 10px;
}

javascript

var dragged;

/* 可拖动的目标元素会触发事件 */
document.addEventListener("drag", function( event ) {

}, false);

document.addEventListener("dragstart", function( event ) {
  // 保存拖动元素的引用(ref.)
  dragged = event.target;
  // 使其半透明
  event.target.style.opacity = .5;
}, false);

document.addEventListener("dragend", function( event ) {
  // 重置透明度
  event.target.style.opacity = "";
}, false);

/* 放下目标节点时触发事件 */
document.addEventListener("dragover", function( event ) {
  // 阻止默认动作
  event.preventDefault();
}, false);

document.addEventListener("dragenter", function( event ) {
  // 当可拖动的元素进入可放置的目标高亮目标节点
  if ( event.target.className == "dropzone" ) {
      event.target.style.background = "purple";
  }

}, false);

document.addEventListener("dragleave", function( event ) {
  // 当拖动元素离开可放置目标节点,重置其背景
  if ( event.target.className == "dropzone" ) {
      event.target.style.background = "";
  }

}, false);

document.addEventListener("drop", function( event ) {
  // 阻止默认动作(如打开一些元素的链接)
  event.preventDefault();
  // 移动拖动的元素到所选择的放置目标节点
  if ( event.target.className == "dropzone" ) {
      event.target.style.background = "";
      dragged.parentNode.removeChild( dragged );
      event.target.appendChild( dragged );
  }

}, false);

DataTransfer详解

DataTransfer不仅仅用于元素之间信息的传递,同时可以控制拖动的样式,以及传递额外的信息

属性:

  • dropEffect 类型 String
  • effectAllowed 类型 String
  • files 类型 FileList
  • types 类型 DOMStringList

方法:

  • void addElement(in Element element)
  • void clearData([in String type])
  • String getData(in String type)
  • void setData(in String type, in String data)
  • void setDragImage(in nsIDOMElement image, in long x, in long y)

这里只讲解一下dropEffecteffectAllowed因为这里有坑files属性用于从浏览器外部拖入文件时候使用types使用较少这里不提了

提供MDN链接,看完本文后可以查阅剩下的方法和属性:

https://developer.mozilla.org...

dropEffecteffectAllowed用于控制拖动的时候光标角标的样式,过滤不同的类型拖动行为

dropEffect可能的值:

  • copy: 复制到新的位置
  • move: 移动到新的位置.
  • link: 建立一个源位置到新位置的链接.
  • none: 禁止放置(禁止任何操作).

effectAllowed可能的值:

  • copy: 复制到新的位置.
  • move:移动到新的位置 .
  • link:建立一个源位置到新位置的链接.
  • copyLink: 允许复制或者链接.
  • copyMove: 允许复制或者移动.
  • linkMove: 允许链接或者移动.
  • all: 允许所有的操作.
  • none: 禁止所有操作.
  • uninitialized: 缺省值(默认值), 相当于 all.

简单来说在被拖动元素的事件中设置effectAllowed属性表示期待的拖动样式,而在容器元素中设置dropEffect表示容器指定的样式,其他的设置都会被忽视

对于dropEffect只有在dragover事件中中修改值才会影响样式,我用的是Chrome66

对于effectAllowed只有在dragstart事件中有效

例子双方都使用link样式:

let dargElem = document.getElementById('test');
let targetElem = document.getElementById('display');

// 拖动开始事件
dargElem.addEventListener('dragstart', (event) => {
    event.dataTransfer.effectAllowed = 'link';
})

// 悬浮到容器上方的事件
targetElem.addEventListener('dragover', (event) => {
    event.dataTransfer.dropEffect = 'link';
    event.preventDefault();
})

// 拖动事件
targetElem.addEventListener('drop', (event) => {
    console.log('pass')
    event.preventDefault();
})

这个例子中drop事件被触发

例子effectAllowed设置为copydropEffect设置为link:

let dargElem = document.getElementById('test');
let targetElem = document.getElementById('display');

// 拖动开始事件
dargElem.addEventListener('dragstart', (event) => {
    event.dataTransfer.effectAllowed = 'link';
})

// 悬浮到容器上方的事件
targetElem.addEventListener('dragover', (event) => {
    event.dataTransfer.dropEffect = 'link';
    event.preventDefault();
})

// 拖动事件
targetElem.addEventListener('drop', (event) => {
    console.log('pass')
    event.preventDefault();
})

这个例子执行的结果就是拖动时候的角标一直是禁止符号,在容器元素上松开也不会触发drop事件

参考链接

https://developer.mozilla.org...

https://developer.mozilla.org...

https://blog.csdn.net/baidu_3...


ASCll
527 声望13 粉丝