本文目标:将介绍原生JS拖拽事件、属性和方法
1. 拖拽示例
在JS红宝书的20.6.5小节 可拖动能力
中提到:
- JS中文字、图片、链接默认允许拖动,但文字需要鼠标选中才可以拖动
- 所有标签均可以设置draggable属性,值若为true,则可以直接拖拽;若draggable属性为true,则文字可以直接进行拖拽,不需要选中
如下dom结构展示了没有设置draggable属性的文字
、链接
、图片
、设置了draggable为true的的文字
四种情况
html
<div>
文字选中拖动
</div>
<div>
<a href="www.baidu.com">我是a标签链接</a>
</div>
<div class="img-box">
<img src="./hs.png" alt="">
</div>
<!-- 我是一个普通的div盒子 -->
<div draggable="true">我是draggable属性为true的div盒子</div>
如下图,第一行文字要选中才能拖动;其余情况鼠标都能直接拖动:
2. 拖拽事件
拖拽事件如下:
- dragstart: 从目标元素身上拖拽起来触发
- drag: 拖拽移动过程中触发
- dragend: 拖拽结束时触发,以上三种在拖拽对象上进行绑定
- dragenter: 拖拽刚刚进入'目标对象'身上触发
- dragover: 拖拽不断在'目标对象'身上移动触发
- dragleave:拖拽离开'目标对象'身上
- drop:拖拽放到'目标对象'身上,以上四种在放置目标对象上实现
如下dom结构,左侧是待拖拽对象,右侧是目标对象(目标对象就是要拖拽到他身上
html
<div class="box">
<ul class="list">
<li draggable="true">我是拖拽目标1</li>
<li draggable="true">我是拖拽目标2</li>
<li draggable="true">我是拖拽目标3</li>
<li draggable="true">我是拖拽目标4</li>
</ul>
<ul class="list2">
<li draggable="true">我是放置目标对象1</li>
<li draggable="true">我是放置目标对象2</li>
<li draggable="true">我是放置目标对象3</li>
<li draggable="true">我是放置目标对象4</li>
</ul>
</div>
样式:
css
.box {
width: 400px;
display: flex;
justify-content: space-between;
}
.list,
.list2 {
border: 1px solid black;
list-style: none;
padding-left: 0;
}
li {
width: 100px;
border-bottom: 1px solid black;
}
给拖拽对象每个小li绑定dragstart
、drag
、dragend
事件
js
liArr.forEach(li => {
li.addEventListener('dragstart', (event) => {
console.log('dragstart');
})
li.addEventListener('drag', (event) => {
console.log('drag');
})
li.addEventListener('dragend', (event) => {
console.log('dragend');
})
})
执行时机为dragstart -> drag -> dragend,效果如下: 给拖拽目标对象绑定dragenter
dragover
dragleave/drop
js
// 拖拽目标绑定
let droptarget = document.querySelector('.list2')
droptarget.addEventListener('dragenter', (event) => {
console.log('dragenter');
})
droptarget.addEventListener('dragover', (event) => {
console.log('dragover');
})
droptarget.addEventListener('dragleave', (event) => {
console.log('dragleave');
})
droptarget.addEventListener('drop', (event) => {
console.log('drop');
})
此时执行时机dragenter -> dragover -> drop,效果如下:
我们发现,鼠标在目标对象上松开了,但是并没有触发drop事件(dragleave可以触发),这是为什么?
原因是元素默认都是不允许放置的,必须得覆盖dragenter和dragover的默认事件,才可以触发,
diff
droptarget.addEventListener('dragenter', (event) => {
+ event.preventDefault()
console.log('dragenter');
})
droptarget.addEventListener('dragover', (event) => {
+ event.preventDefault();
console.log('dragover');
})
如下图,现在可以触发了:
总结:拖拽共有两组类型的事件:
- 第一组是刚开始拖拽时的dragstart drag dragend,其需要给拖拽对象绑定;
- 第二组是dragenter dragover dragleave drop事件,需要绑定在拖拽放置目标上;
- 比如把A拖拽到B上,需要给A绑定
dragstart drag dragend
事件,给B绑定dragenter dragover dragleave drop
事件
3. 拖拽传递数据:dataTransfer对象 --> getData setData
存储数据用dataTransfer对象
的setData
,获取数据用dataTransfer对象
的getData
方法
若把文本框input数据拖拽到其他容器内,浏览器会默认在后台调用setData('text',文本框的内容),我们可以在drop事件中拿到数据:
dom结构如下:
html
<div class="box">
<div class="wenben">
<input type="text" value="被拖动元素" id="ipt">
</div>
<ul class="list2">
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
</div>
css
.box {
width: 200px;
display: flex;
justify-content: space-between;
align-items: center;
}
#ipt {
margin-right: 10px;
}
.list2 {
border: 1px solid black;
list-style: none;
padding-left: 0;
}
.list2 li {
width: 50px;
border-bottom: 1px solid black;
}
此时并未设置setData, 在drop事件里面手动调用getData
方法拿到文本框的内容,key默认是text。注意,dragleave拿不到数据,但是drop能够拿到
js
droptarget.addEventListener('drop', (event) => {
// dragleave拿不到数据,但是drop能够拿到
console.log(event.dataTransfer.getData('text'), 'drop event.dataTransfer.getData');
})
效果如下,控制台打印的是文本框里面的值:
若要手动设置值,则需要在dragstart
事件中手动存储数据,通过setData
,代码如下:
js
// 拿到ipt元素
let ipt = document.querySelector('#ipt')
ipt.addEventListener('dragstart', (event) => {
event.dataTransfer.setData('name', ipt.value)
})
droptarget.addEventListener('drop', (event) => {
// dragleave拿不到数据,但是drop能够拿到
console.log(event.dataTransfer.getData('text'), 'drop event.dataTransfer.getData');
})
效果如下,注意,getData的数据在dragstart和drop事件中才能拿到
,其他事件我测试了均未能拿到
4. effectAllowed和dropEffect属性
- effectAllowed:拖拽对象身上调用,把A拖拽到B,要在A身上设置effectAllowed属性,表示这个元素可以进行哪些操作,要把他移动走还是复制走
- dropEffect:拖拽的目标对象身上调用,假设把A拖拽到B身上,要在B身上使用dropEffect属性,告诉别人,你可以往我这里做什么操作,比如可以复制过来或者移动过来
- 这两个属性要同时设置
如果希望别人禁止拖拽元素过来:
diff
linkTarget.addEventListener('dragover', (event) => {
+ event.dataTransfer.dropEffect = 'none'
event.preventDefault()
})
效果如下,此时drop事件也不会触发:
把元素复制出去,复制光标:
diff
// 把list2改变成允许拖动的目标
let droptarget = document.querySelector('.list2')
droptarget.addEventListener('dragenter', (event) => {
event.preventDefault()
})
droptarget.addEventListener('dragover', (event) => {
+ event.dataTransfer.dropEffect = 'copy'
event.preventDefault()
})
droptarget.addEventListener('drop', (event) => {
console.log(event.dataTransfer, 'event.dataTransfer');
})
// 拿到ipt元素
let ipt = document.querySelector('#ipt')
ipt.addEventListener('dragstart', (event) => {
+ event.dataTransfer.effectAllowed = 'copy'
// console.log(event.dataTransfer, 'event.dataTransfer dragstart');
})
把元素移动出去,移动光标:
diff
// 把list2改变成允许拖动的目标
let droptarget = document.querySelector('.list2')
droptarget.addEventListener('dragenter', (event) => {
event.preventDefault()
})
droptarget.addEventListener('dragover', (event) => {
+ event.dataTransfer.dropEffect = 'move'
event.preventDefault()
})
droptarget.addEventListener('drop', (event) => {
console.log(event.dataTransfer, 'event.dataTransfer');
})
// 拿到ipt元素
let ipt = document.querySelector('#ipt')
ipt.addEventListener('dragstart', (event) => {
+ event.dataTransfer.effectAllowed = 'move'
// console.log(event.dataTransfer, 'event.dataTransfer dragstart');
})