jQuery dragenter或dragover包括子元素

12
我正在研究一个上传脚本,当然它具备拖放功能。
不过,我正在尝试使文件拖动到我的元素上时添加类名为drag-over,但由于我的元素有子元素,所以它会不断触发,因为它会进入和离开该元素。
我想知道的是如何扩展*dragenter* / *dragover* 以包括主要元素的子元素?
以下是我代码的精简版本(请注意我已禁用文件输入):
$(document).ready(function(){
    $(window).on('dragenter', function(){
        $(this).preventDefault();
    });
    $('#drag-and-drop-zone').on('dragenter', function(){
        $(this).addClass('drag-over');
    });
    $('#drag-and-drop-zone').on('dragleave', function(){
        $(this).removeClass('drag-over');
    });
});
.uploader
{
    width: 100%;
    background-color: #f9f9f9;
    color: #92AAB0;
    text-align: center;
    vertical-align: middle;
    padding: 30px 0px;
    margin-bottom: 10px;
    border-radius: 5px;
    font-size: 200%;
    box-shadow: inset 0px 0px 20px #c9afb2;
    cursor: default;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

.uploader div.or {
    font-size: 50%;
    font-weight: bold;
    color: #C0C0C0;
    padding: 10px;
}

.uploader div.browser label {
    background-color: #ffffff;
    border: 2px solid #f44;
    padding: 5px 15px;
    color: #f44;
    padding: 6px 0px;
    font-size: 40%;
    font-weight: bold;
    cursor: pointer;
    border-radius: 2px;
    position: relative;
    overflow: hidden;
    display: block;
    width: 300px;
    margin: 20px auto 0px auto;
    transition: all 0.3s linear 0s;
}

.uploader div.browser span {
    cursor: pointer;
}

.uploader div.browser input {
    position: absolute;
    top: 0;
    right: 0;
    margin: 0;
    border: solid transparent;
    border-width: 0 0 100px 200px;
    opacity: .0;
    filter: alpha(opacity= 0);
    direction: ltr;
    cursor: pointer;
}

.uploader div.browser label:hover {
    background-color: #f44;
    color: #fff;
    border: 2px solid #fff;
}

.drag-over{
    border: 2px solid #00aef0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<div class="uploader" id="drag-and-drop-zone">
    <div>Drag &amp; Drop Images Here</div>
    <div class="or">-or-</div>
    <div class="browser">
        <label>
            <span>Select Image</span>
            <input type="file" title="Click to add Images" accept="image/*" name="files" disabled="true">
        </label>
    </div>
</div>


第三行可能出现错误:$(this).preventDefault(); 你可能想传入事件对象,然后调用event.preventDefault()。 - Kenneth Hippolite
4个回答

18

解决了!!

这只是一个简单的问题,原来应该使用 bind('dragover') 而不是 on('dragenter')

$(document).ready(function(){
    $(window).on('dragenter', function(){
        $(this).preventDefault();
    });
    $('#drag-and-drop-zone').bind('dragover', function(){
        $(this).addClass('drag-over');
    });
    $('#drag-and-drop-zone').bind('dragleave', function(){
        $(this).removeClass('drag-over');
    });
});
.uploader
{
    width: 100%;
    background-color: #f9f9f9;
    color: #92AAB0;
    text-align: center;
    vertical-align: middle;
    padding: 30px 0px;
    margin-bottom: 10px;
    border-radius: 5px;
    font-size: 200%;
    box-shadow: inset 0px 0px 20px #c9afb2;
    cursor: default;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

.uploader div.or {
    font-size: 50%;
    font-weight: bold;
    color: #C0C0C0;
    padding: 10px;
}

.uploader div.browser label {
    background-color: #ffffff;
    border: 2px solid #f44;
    padding: 5px 15px;
    color: #f44;
    padding: 6px 0px;
    font-size: 40%;
    font-weight: bold;
    cursor: pointer;
    border-radius: 2px;
    position: relative;
    overflow: hidden;
    display: block;
    width: 300px;
    margin: 20px auto 0px auto;
    transition: all 0.3s linear 0s;
}

.uploader div.browser span {
    cursor: pointer;
}

.uploader div.browser input {
    position: absolute;
    top: 0;
    right: 0;
    margin: 0;
    border: solid transparent;
    border-width: 0 0 100px 200px;
    opacity: .0;
    filter: alpha(opacity= 0);
    direction: ltr;
    cursor: pointer;
}

.uploader div.browser label:hover {
    background-color: #f44;
    color: #fff;
    border: 2px solid #fff;
}

.drag-over{
    border: 2px solid #00aef0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<div class="uploader" id="drag-and-drop-zone">
    <div>Drag &amp; Drop Images Here</div>
    <div class="or">-or-</div>
    <div class="browser">
        <label>
            <span>Select Image</span>
            <input type="file" title="Click to add Images" accept="image/*" name="files" disabled="true">
        </label>
    </div>
</div>


1
为什么 bind 能够工作,但 on 却不能?我被告知要使用 $.fn.on 来处理所有的监听器。 - TarranJones
这个不正常工作,目的是拥有一个恒定的dragover类,这个解决方案有一个刷新行为,在没有dragover类的情况下有一些闪烁。 - jave.web

7

显然,这个问题比我想象的更加常见,因为我至少找到了5个与相同主题相关的问题。

与“mouseover”不同,“dragover”和“dragleave”事件不将子元素视为整体,因此每次鼠标经过任何子元素时,“dragleave”都会被触发。

考虑到文件上传,我创建了一个小部件,可以实现以下功能:

  1. 使用$ _FILES拖放桌面文件
  2. 使用$ _POST和cURL在浏览器中拖放图像/元素或URL
  3. 使用按钮附加设备文件,使用$ _FILES
  4. 使用输入框编写/粘贴URL图像/元素,使用$ _POST和cURL

enter image description here

问题:由于所有的表单输入和图片都在 DIV 的子元素中,即使它没有离开虚线范围,“dragleave”也会被触发。使用属性“pointer-events: none”不是一个选择,因为方法3和4需要触发“onchange”事件。
解决方案?当鼠标进入时,一个重叠的 DIV 覆盖了所有的 drop-container,并且只有带有“pointer-events: none”的子元素。
结构如下:
:主要的 DIV,保持整洁
:“dragenter”监听器和立即触发#drop-pupup
:与#drop-area处于同一级别,“dragenter”,“dragleave”和“drop”监听器
然后,当鼠标通过将元素拖到#drop-area中进入时,立即显示#drop-pupup,接下来的事件发生在这个 DIV 上而不是最初的接收者。

enter image description here

以下是JS / jQuery代码。我冒昧留下PoC,以免浪费我所花费的所有时间。

jQuery(document).on('dragover', '#drop-area', function(event) {
 event.preventDefault();
 event.stopPropagation();
 jQuery('#drop-popup').css('display','block');
});

jQuery(document).on('dragover dragleave drop', '#drop-popup', function(event) {
 event.preventDefault();
 event.stopPropagation();

 console.log(event.type);

 // layout and drop events
 if ( event.type == 'dragover') {
  jQuery('#drop-popup').css('display','block');
 }
 else {
  jQuery('#drop-popup').css('display','none');
 
  if ( event.type == 'drop' ) {
   // do what you want to do
   // for files: use event.originalEvent.dataTransfer.files
   // for web dragged elements: use event.originalEvent.dataTransfer.getData('Text') and CURL to capture
  }
 }
});
body {
  background: #ffffff;
  margin: 0px;
  font-family: sans-serif;
}

#drop-container {
  margin: 100px 10%; /* for online testing purposes only */
  width: 80%; /* for jsfiddle purposes only */
  display: block;
  float: left;
  overflow: hidden;
  box-sizing: content-box;
  position: relative; /* needed to use absolute on #drop-popup */
  border-radius: 5px;
  text-align: center;
  cursor: default;
  border: 2px dashed #000000;
}

#drop-area {
  display: block;
  float: left;
  padding: 10px;
  width: 100%;
}

#drop-popup {
  display: none;
  box-sizing: content-box;
  position: absolute;
  width: 100%;
  top: 0;
  left: 0;
  background: linear-gradient(to BOTTOM, rgba(245, 245, 245, 1) , rgba(245, 245, 245, 0));
  height: 512px;
  padding: 20px;
  z-index: 20;
}

#drop-popup > p {
   pointer-events: none;
}
<html>
  <head>
    <title>Drag and Drop</title>
  </head>
  <body>

    <div id="drop-container">
      <div id="drop-area">
        <p>Child paragraph content inside drop area saying "drop a file or an image in the dashed area"</p>
        <div>This is a child div No. 1</div>
        <div>This is a child div No. 2</div>
      </div>
      <div id="drop-popup">
        <p>This DIV will cover all childs on main DIV dropover event and current P tag is the only one with CSS "pointer-events: none;"</p>
      </div>
    </div>
    
    <script src="https://code.jquery.com/jquery-3.4.1.min.js" type="text/javascript"></script>
  </body>
<html>

关于jQuery的"on"方法,将它与div id一起使用,这样你就可以在"上传框"隐藏时开始事件触发。
最后,我更喜欢使用"dragover"而不是"dragenter",因为它有一个小延迟(毫秒级),有利于性能(https://developer.mozilla.org/en-US/docs/Web/API/Document/dragover_event)。

1

我找到了另外2个可行的解决方案。

只有在您区域内没有其他控制器元素(例如编辑、删除)时,此解决方案才有效,因为此解决方案也会阻止它们:

#drop * {pointer-events: none;}

有一个更好的解决方案

这个想法是每次进入/悬停在一个新的子元素时增加计数器,离开其中一个子元素时减少计数器。

$(document).ready(function(){

    var dropzoneCounter = 0;

    $('#drag-and-drop-zone').on('dragenter', function(){
        dropzoneCounter++;
        $(this).addClass('drag-over');
    });

    $('#drag-and-drop-zone').bind('dragleave', function(){
        dropzoneCounter--;
        if (dropzoneCounter === 0) {
            $(this).removeClass('drag-over');
        }
    });

    $('#drag-and-drop-zone').bind('drop', function(){
        dropzoneCounter = 0;
        $(this).removeClass('drag-over');
    });
});

0

您可以通过样式简单地隐藏元素,使其无法与鼠标进行交互:

例如,将此代码添加到子元素中:

pointer-events: none;

很不幸,在IE浏览器中对于这个功能的支持并不是很好:http://caniuse.com/#feat=pointer-events


理想情况下,我希望有一个跨浏览器的解决方案,而不是“这些可以工作,但这些不行”的解决方案。这是生产级别的代码,所以必须是100%可靠的 - 谢谢你的回答! - JustSteveKing
1
@JustSteveKing:是的,对此我感到抱歉。另一种选择是在拖动时更复杂地检查鼠标所在位置(是否在父级内等)。 - iCollect.it Ltd
谢谢,我正在忙于一些事情。我在考虑在 on dragover 被触发之前将子元素绑定到父元素。 - JustSteveKing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接