一、拖放
拖放是一种常见的特性,即抓取对象以后拖到另一个位置。所以其中包含的动作也是两个:拖(拽 drag)和放(drop)
在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放。其属性为draggable:true,当值为true时,为可拖拽
二、数据
DataTransfer对象用于保存拖放过程中的数据,其数据可以为一项或多项,数据项也可为一种或多种数据类型。
在我们实现拖拽上传时就用来获取所拖拽文件的列表。
三、事件
既然拖放是分为拖拽部分和释放部分,则我们可以将目标划分为拖拽元素和要放置的目标元素。
| 事件名称 | 时间 | 作用目标 |
|---|---|---|
| ondragstart | 拖拽元素开始被拖拽时触发 | 拖拽元素 |
| ondragend | 拖拽完成后触发 | 拖拽元素 |
| ondragenter | 拖拽元素进入目标元素时触发 | 目标元素 |
| ondragover | 拖拽元素在目标元素上移动时触发 | 目标元素 |
| ondragleave | 当拖拽元素没有放下就离开目标元素时触发 | 目标元素 |
| ondrop | 拖拽元素在目标元素上同时鼠标放开触发 | 目标元素 |
注意:在拖动元素时,每隔 350 毫秒会触发 ondragover 事件。
在ondragover事件中,一定要执行阻止默认事件执行的方法:Event.preventDefault()方法,否则ondrop事件是不会触发的,对于文件拖拽来说,其默认动作是显示图片信息或下载相关文件,所以要阻止默认行为,从而执行我们在ondrop中的上传操作。
四、遇到的问题
在做文件拖拽上传的过程中,在文件拖入目标容器时,需要显示遮罩层进行提示“释放鼠标以上传”,目标容器有子div,对目标容器进行ondragover,ondragenter,ondrop事件监听时,会出现遮罩层频闪的问题。
问题一:
当时是在项目中出现的,所以想写一个小的Demo进行演示,首先是只写了一个目标容器,一个遮罩层,尝试进行文件拖动,结果发现依然会出现频闪的现象。浏览网上关于拖拽事件的测试都是正常的,因为他们大多都是通过改变css属性来进行测试,并不是通过遮罩层来提示,而遮罩层又是通过display属性来进行控制,所以其实是因为拖入目标容器,触发dragenter,但由于此时遮罩层显示,就会触发dragleave,如此循环就造成了频闪的现象,其实是拖拽操作和遮罩显隐互相影响的结果。
\
解决办法:因为遮罩层属于提示作用,则可以降低其层级,这样保证拖动文件时,都在目标容器中,从而消除互相影响的效果。
问题二:
当尝试在目标容器中增加子容器时,发现从下往上拖动文件的过程中,是有遮罩层(在目标容器)——没有遮罩层(在子容器)——没有遮罩层(在目标容器),根据控制台的输出,可以发现是有规律可循的:
在进入一个容器时,会退出上一个所在容器,而在最后一次出界目标容器的时候,就只有退出事件了。所以要解决这个问题,可以对最后进入的元素进行保存,只有退出的元素是最后进入的元素,才说明是真的退出了目标容器
五、总结
1、在ondragover事件中阻止默认事件执行,才可以触发ondrop事件
2、在拖拽时要注意目标容器的层级,以免受到影响
3、在有子容器的时候,需要进行判断什么时候才是真正的退出了目标容器,可以通过保存最后进入的容器,和dragleave是的target进行比较,一致时便是退出的时候。
六、补充
关于遮罩层实现:
1、通过增加一个div来实现(如上)
2、通过伪类来实现:
<!--结构-->
<div id="drag_zone" data-attr="释放鼠标以上传"
ondragenter="onDragEnter(event)"
ondragover="onDragOver(event)"
ondragleave="onDragLeave(event)"
ondrop="onDrop(event)">
<div id="drag_child"></div>
</div>
<!--伪类-->
<style>
.drag_zone:after{
content: attr(data-attr);
width: 500px;
height: 400px;
background-color: deepskyblue;
opacity: 0.1;
position: absolute;
display: block;
z-index: -1;
top: 100px;
left: 109px;
text-align: center;
}
</style>
<!--动态添加css控制遮罩层出现-->
<script>
var lastenter = null;
function onDragEnter(ev){
lastenter = ev.target;
document.getElementById('drag_zone').setAttribute('class','drag_zone');
console.log('onDragEnter,进入元素为',ev.target.id);
}
function onDragOver(ev){
ev.preventDefault();
}
function onDragLeave(ev){
if(lastenter === ev.target){
ev.preventDefault();
ev.stopPropagation();
document.getElementById('drag_zone').removeAttribute('class')
console.log('onDragLeave,退出元素为',ev.target.id)
}
}
function onDrop(ev){
ev.preventDefault();
console.log('onDrop',ev.target.id);
document.getElementById('drag_zone').removeAttribute('class')
console.log(ev.dataTransfer);
}
</script>
关于受层级影响的解决办法:
使用CSS属性pointer-events,设置为none,表示元素永远不会成为鼠标事件的target,鼠标事件“穿透”该元素。
#mask{
width: 500px;
height: 400px;
background-color: deepskyblue;
opacity: 0.1;
position: absolute;
display: none;
top: 170px;
left: 109px;
pointer-events: none;
}