darg事件踩坑记录

1,176 阅读4分钟

一、拖放

拖放是一种常见的特性,即抓取对象以后拖到另一个位置。所以其中包含的动作也是两个:拖(拽 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;
    }