元素。问题当我将文件拖到
概述
我有以下 HTML 结构,并将 dragenter
和 dragleave
事件附加到 <div id="dropzone">
元素。
<div id="dropzone">
<div id="dropzone-content">
<div id="drag-n-drop">
<div class="text">this is some text</div>
<div class="text">this is a container with text and images</div>
</div>
</div>
</div>
问题
当我将文件拖到 <div id="dropzone">
上时,会按预期触发 dragenter
事件。但是,当我将鼠标移到子元素(例如 <div id="drag-n-drop">
)上时,会为 <div id="drag-n-drop">
元素触发 dragenter
事件,然后为 <div id="dropzone">
元素触发 dragleave
事件。
如果我再次将鼠标悬停在 <div id="dropzone">
元素上,则会再次触发 dragenter
事件,这很酷,但是随后会为刚刚离开的子元素触发 dragleave
事件,因此会执行 removeClass
指令,这不酷。
这种行为是有问题的,原因有两个:
我只是将 dragenter 和 dragleave 附加到
jsFiddle
这是一个要修补的 jsFiddle:http://jsfiddle.net/yYF3S/2/
问题
所以......我怎样才能做到这样,当我将文件拖到 <div id="dropzone">
元素上时,即使我拖过任何子元素,dragleave
也不会触发......它应该只在以下情况下触发我留下 <div id="dropzone">
元素...在元素边界内的任何地方悬停/拖动不应该触发 dragleave
事件。
我需要它是跨浏览器兼容的,至少在支持 HTML5 拖放的浏览器中,所以 this answer 是不够的。
似乎 Google 和 Dropbox 已经解决了这个问题,但是他们的源代码被缩小/复杂,所以我无法从他们的实现中解决这个问题。
e.stopPropagation();
传播事件
如果您不需要将事件绑定到子元素,则始终可以使用 pointer-events 属性。
.child-elements {
pointer-events: none;
}
我终于找到了一个我满意的解决方案。我实际上找到了几种方法来做我想做的事,但没有一种方法像当前的解决方案那样成功......在一个解决方案中,由于向 #dropzone
元素添加/删除边框而导致我经常闪烁......在另一个解决方案中,如果您将鼠标悬停在浏览器之外,则永远不会删除边框。
无论如何,我最好的 hacky 解决方案是:
var dragging = 0;
attachEvent(window, 'dragenter', function(event) {
dragging++;
$(dropzone).addClass('drag-n-drop-hover');
event.stopPropagation();
event.preventDefault();
return false;
});
attachEvent(window, 'dragover', function(event) {
$(dropzone).addClass('drag-n-drop-hover');
event.stopPropagation();
event.preventDefault();
return false;
});
attachEvent(window, 'dragleave', function(event) {
dragging--;
if (dragging === 0) {
$(dropzone).removeClass('drag-n-drop-hover');
}
event.stopPropagation();
event.preventDefault();
return false;
});
这工作得很好,但是在 Firefox 中出现了问题,因为 Firefox 是双重调用 dragenter
,所以我的计数器关闭了。但是,它不是一个非常优雅的解决方案。
然后我偶然发现了这个问题:How to detect the dragleave event in Firefox when dragging outside the window
所以我拿了 the answer 并将其应用于我的情况:
$.fn.dndhover = function(options) {
return this.each(function() {
var self = $(this);
var collection = $();
self.on('dragenter', function(event) {
if (collection.size() === 0) {
self.trigger('dndHoverStart');
}
collection = collection.add(event.target);
});
self.on('dragleave', function(event) {
/*
* Firefox 3.6 fires the dragleave event on the previous element
* before firing dragenter on the next one so we introduce a delay
*/
setTimeout(function() {
collection = collection.not(event.target);
if (collection.size() === 0) {
self.trigger('dndHoverEnd');
}
}, 1);
});
});
};
$('#dropzone').dndhover().on({
'dndHoverStart': function(event) {
$('#dropzone').addClass('drag-n-drop-hover');
event.stopPropagation();
event.preventDefault();
return false;
},
'dndHoverEnd': function(event) {
$('#dropzone').removeClass('drag-n-drop-hover');
event.stopPropagation();
event.preventDefault();
return false;
}
});
这是干净和优雅的,似乎在我迄今为止测试过的每个浏览器中都可以使用(还没有测试过 IE)。
stopPropagation()
和 preventDefault()
。我会放弃 return false
。
这有点难看,但它确实有效!...
在你的'dragenter'处理程序上存储event.target(在你的闭包内的一个变量中,或其他),然后在你的'dragleave'处理程序中,只有在event.target ===你存储的那个时才会触发你的代码。
如果你的'dragenter'在你不想要它的时候触发(即当它在离开子元素后进入时),那么它在鼠标离开父元素之前最后一次触发,它在父元素上,所以父元素将永远是预期的“dragleave”之前的最后一个“dragenter”。
(function () {
var droppable = $('#droppable'),
lastenter;
droppable.on("dragenter", function (event) {
lastenter = event.target;
droppable.addClass("drag-over");
});
droppable.on("dragleave", function (event) {
if (lastenter === event.target) {
droppable.removeClass("drag-over");
}
});
}());
起初,我同意人们放弃 pointer-events: none
方法。但后来我问自己:
在拖动过程中,您真的需要指针事件来处理子元素吗?
在我的例子中,我在孩子身上发生了很多事情,例如,悬停以显示用于其他操作的按钮、内联编辑等......但是,在拖动过程中,这些都不是必需的,甚至实际上是不需要的。
在我的例子中,我使用这样的东西来选择性地关闭父容器的所有子节点的指针事件:
div.drag-target-parent-container.dragging-in-progress * {
pointer-events: none;
}
使用您最喜欢的方法在 dragEnter
/dragLeave
事件处理程序中添加/删除类 dragging-in-progress
,就像我在 dragStart
中所做或所做的一样,等等。人。
dragstart
事件中添加类并在 dragend
中将其删除。
这似乎是一个 Chrome 错误。
我能想到的唯一解决方法是创建一个透明的叠加元素来捕获您的事件:http://jsfiddle.net/yYF3S/10/
JS:
$(document).ready(function() {
var dropzone = $('#overlay');
dropzone.on('dragenter', function(event) {
$('#dropzone-highlight').addClass('dnd-hover');
});
dropzone.on('dragleave', function(event) {
$('#dropzone-highlight').removeClass('dnd-hover');
});
});
HTML:
<div id="dropzone-highlight">
<div id="overlay"></div>
<div id="dropzone" class="zone">
<div id="drag-n-drop">
<div class="text1">this is some text</div>
<div class="text2">this is a container with text and images</div>
</div>
</div>
</div>
<h2 draggable="true">Drag me</h2>
问题是,放置区内的元素当然是放置区的一部分,当您进入子级时,您会离开父级。解决这个问题并不容易。您可以尝试向孩子添加事件,也可以将您的班级再次添加到父母。
$("#dropzone,#dropzone *").on('dragenter', function(event) {
// add a class to #dropzone
event.stopPropagation(); // might not be necessary
event.preventDefault();
return false;
});
您的事件仍会触发多次,但没有人会看到。
//编辑:使用dragmove事件永久覆盖dragleave事件:
$("#dropzone,#dropzone *").on('dragenter dragover', function(event) {
// add a class to #dropzone
event.stopPropagation(); // might not be necessary
event.preventDefault();
return false;
});
仅为 dropzone 定义 dragleave 事件。
dragleave
...当我离开一个孩子时,我可能仍然在父 #dropzone
中,但这不会再次触发 dragenter
事件:(
dragleave
事件,因此再次执行了removeClass
指令
#dropzone
定义 dragleave
...如果可以,我就不会有这个问题。
如 this answer 中的 benr 所述,您可以阻止子节点触发事件,但如果您需要绑定某些事件,请执行以下操作:
#dropzone.dragover *{
pointer-events: none;
}
并将这个添加到您的 JS 代码中:
$("#dropzone").on("dragover", function (event) {
$("#dropzone").addClass("dragover");
});
$("#dropzone").on("dragleave", function (event) {
$("#dropzone").removeClass("dragover");
});
如果您使用的是 jQuery,请查看:https://github.com/dancork/jquery.event.dragout
真是太棒了。
为处理真正的拖动离开功能而创建的特殊事件。 HTML5 dragleave 事件更像 mouseout。创建此插件是为了在拖动时复制 mouseleave 样式功能。使用示例: $('#myelement').on('dragout',function(event){ // 你的代码 });
编辑:实际上,我认为它不依赖于 jQuery,即使没有它,您也可以只使用代码。
dragleave
。
dragout
是唯一对我有用的解决方案。
我的两分钱:在 dropzone 上隐藏一个图层,然后在 dragenter 时显示它,并将 dragleave 定位在它上面。
演示:https://jsfiddle.net/t6q4shat/
HTML
<div class="drop-zone">
<h2 class="drop-here">Drop here</h2>
<h2 class="drop-now">Drop now!</h2>
<p>Or <a href="#">browse a file</a></p>
<div class="drop-layer"></div>
</div>
CSS
.drop-zone{
padding:50px;
border:2px dashed #999;
text-align:center;
position:relative;
}
.drop-layer{
display:none;
position:absolute;
top:0;
left:0;
bottom:0;
right:0;
z-index:5;
}
.drop-now{
display:none;
}
JS
$('.drop-zone').on('dragenter', function(e){
$('.drop-here').css('display','none');
$('.drop-now').css('display','block');
$(this).find('.drop-layer').css('display','block');
return false;
});
$('.drop-layer').on('dragleave', function(e){
$('.drop-here').css('display','block');
$('.drop-now').css('display','none');
$(this).css('display','none');
return false;
});
@hristo 我有一个更优雅的解决方案。检查这是否是您可以使用的东西。
毕竟你的努力没有白费。起初我设法使用了你的,但在 FF、Chrome 中遇到了不同的问题。在花了这么多小时后,我得到了这个建议完全按照预期工作。
这是实现的方式。我还利用视觉提示来正确引导用户了解放置区。
$(document).on('dragstart dragenter dragover', function(event) {
// Only file drag-n-drops allowed, http://jsfiddle.net/guYWx/16/
if ($.inArray('Files', event.originalEvent.dataTransfer.types) > -1) {
// Needed to allow effectAllowed, dropEffect to take effect
event.stopPropagation();
// Needed to allow effectAllowed, dropEffect to take effect
event.preventDefault();
$('.dropzone').addClass('dropzone-hilight').show(); // Hilight the drop zone
dropZoneVisible= true;
// http://www.html5rocks.com/en/tutorials/dnd/basics/
// http://api.jquery.com/category/events/event-object/
event.originalEvent.dataTransfer.effectAllowed= 'none';
event.originalEvent.dataTransfer.dropEffect= 'none';
// .dropzone .message
if($(event.target).hasClass('dropzone') || $(event.target).hasClass('message')) {
event.originalEvent.dataTransfer.effectAllowed= 'copyMove';
event.originalEvent.dataTransfer.dropEffect= 'move';
}
}
}).on('drop dragleave dragend', function (event) {
dropZoneVisible= false;
clearTimeout(dropZoneTimer);
dropZoneTimer= setTimeout( function(){
if( !dropZoneVisible ) {
$('.dropzone').hide().removeClass('dropzone-hilight');
}
}, dropZoneHideDelay); // dropZoneHideDelay= 70, but anything above 50 is better
});
var dropZoneHideDelay=70, dropZoneVisible=true;
'on'
文档事件都需要这两个 dropZoneHideDelay, dropZoneVisible
。
这个答案可以在这里找到:
HTML5 dragleave fired when hovering a child element
var counter = 0;
$('#drop').bind({
dragenter: function(ev) {
ev.preventDefault(); // needed for IE
counter++;
$(this).addClass('red');
},
dragleave: function() {
counter--;
if (counter === 0) {
$(this).removeClass('red');
}
}
});
我的版本:
$(".dropzone").bind("dragover", function(e){
console.log('dragover');
});
$(".dropzone").bind("dragleave", function(e) {
var stopDrag = false;
if (!e.relatedTarget) stopDrag = true;
else {
var parentDrop = $(e.relatedTarget).parents('.dropzone');
if (e.relatedTarget != this && !parentDrop.length) stopDrag = true;
}
if (stopDrag) {
console.log('dragleave');
}
});
使用此布局:
<div class="dropzone">
<div class="inner-zone">Inner-zone</div>
</div>
我为 dragover
和 dragleave
事件为 e.target
、e.currentTarget
、e.relatedTarget
制作了元素类转储。
它告诉我,离开父块 (.dropzone
) e.relatedTarget
不是这个块的子块,所以我知道我已经离开了放置区。
!e.relatedTarget
非常适合我。谢谢@mortalis
抱歉,它是 javascript 而不是 jquery,但对我来说,这是解决这个问题的最合乎逻辑的方法。浏览器应该在 dropenter (新元素的)之前调用 dropleave (前一个元素的),因为在离开前一个元素之前不能输入其他东西,我不明白他们为什么这样做!所以你只需要延迟像这样删除:
function mydropleave(e)
{
e.preventDefault();
e.stopPropagation();
setTimeout(function(e){ //the things you want to do },1);
}
dropenter 将在 dropleave 之后发生,仅此而已!
所以对我来说,方法 pointer-events: none;
效果不太好......所以这是我的替代解决方案:
#dropzone {
position: relative;
}
#dropzone(.active)::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: '';
}
这样,就不可能 dragleave
父元素(在子元素上)或 dragover
子元素。希望这可以帮助 :)
*我在 dragenter
或 dragleave
时添加的“.active”类。但是,如果您在没有它的情况下工作,请离开课程。
我对这里介绍的任何解决方法都不满意,因为我不想失去对子元素的控制。
所以我使用了一种不同的逻辑方法,将其转换为一个名为 jquery-draghandler 的 jQuery 插件。它绝对不操纵 DOM,保证高性能。它的用法很简单:
$(document).ready(function() {
$(selector).draghandler({
onDragEnter: function() {
// $(this).doSomething();
},
onDragLeave: function() {
// $(this).doSomethingElse();
}
});
});
它在不影响任何 DOM 功能的情况下完美地处理了该问题。
在其 Git repository 上下载、详细信息和解释。
frame
并且您的元素位于它的边界。在这种情况下,我不建议这种方法。
超级简单的快速修复,未经广泛测试,但现在可以在 Chrome 中使用。
对不起Coffeescript。
dragEndTimer = no
document.addEventListener 'dragover', (event) ->
clearTimeout dragEndTimer
$('body').addClass 'dragging'
event.preventDefault()
return no
document.addEventListener 'dragenter', (event) ->
$('section').scrollTop(0)
clearTimeout dragEndTimer
$('body').addClass 'dragging'
document.addEventListener 'dragleave', (event) ->
dragEndTimer = setTimeout ->
$('body').removeClass 'dragging'
, 50
这修复了 Chrome 闪烁错误,或者至少修复了导致我出现问题的它的排列。
我实际上很喜欢我在 https://github.com/lolmaus/jquery.dragbetter/ 看到的内容,但想分享一个可能的替代方案。我的一般策略是在拖放区域或其任何子项(通过冒泡)时将背景样式应用于拖放区(而不是其子项)。然后我在拖动放置区域时删除样式。想法是当移动到一个孩子时,即使我在离开它时从 dropzone 中删除了样式(dragleave 触发),我只会在拖动任何孩子时将样式重新应用到父 dropzone。问题当然是,当从 dropzone 移动到 dropzone 的子级时,dragenter 在 dragleave 之前被触发到子级上,所以我的样式被乱序应用了。对我来说,解决方案是使用计时器将 dragenter 事件强制返回消息队列,允许我在 dragleave 之后处理它。我使用闭包来访问计时器回调上的事件。
$('.dropzone').on('dragenter', function(event) {
(function (event) {
setTimeout(function () {
$(event.target).closest('.dropzone').addClass('highlight');
}, 0);
}) (event.originalEvent);
});
这似乎适用于chrome,即firefox,并且无论dropzone中有多少孩子都可以使用。我对保证事件重新排序的超时有点不安,但它似乎对我的用例很有效。
在这里,最简单的解决方案之一●︿●
看看这个 fiddle < - 尝试在框中拖动一些文件
你可以这样做:
var dropZone= document.getElementById('box');
var dropMask = document.getElementById('drop-mask');
dropZone.addEventListener('dragover', drag_over, false);
dropMask.addEventListener('dragleave', drag_leave, false);
dropMask.addEventListener('drop', drag_drop, false);
这样,您应该已经知道这里发生了什么。看看小提琴,你知道的。
我有一个类似的问题,我用这种方式解决了它:
问题:当用户在“放置区”(ul 元素)中放置一个元素时会触发函数 drop(ev),但不幸的是,当该元素被放置在它的一个子元素(li 元素)中时也会触发该函数。
修复:
function drop(ev) {
ev.preventDefault();
data=ev.dataTransfer.getData('Text');
if(ev.target=="[object HTMLLIElement]")
{ev.target.parentNode.appendChild(document.getElementById(data));}
else{ev.target.appendChild(document.getElementById(data));}
}
我试图自己为一个文件上传框实现这一点,当用户将文件拖到空间中时,框的颜色会改变。
我找到了一个很好地结合了 Javascript 和 CSS 的解决方案。假设您有一个带有 div
且 ID 为 #drop
的可放置区域。将此添加到您的 Javascript:
$('#drop').on('dragenter', function() {
$(this).addClass('dragover');
$(this).children().addClass('inactive');
});
$('#drop').on('dragleave', function() {
$(this).removeClass('dragover');
$(this).children().removeClass('inactive');
});
然后,将此添加到您的 CSS 中,以停用所有子类 .inactive
:
#drop *.inactive {
pointer-events: none;
}
因此,只要用户将元素拖到框上,子元素就会处于非活动状态。
我试图自己实现这个,我也不想要 Jquery 或任何插件。
我想按照最适合我的方式处理文件上传:
文件结构:
--- /uploads {上传目录}
--- /js/slyupload.js {javascript 文件。}
--- index.php
--- 上传.php
--- styles.css {只是一点样式..}
HTML 代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>my Dzone Upload...</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="uploads"></div>
<div class="dropzone" id="dropzone">Drop files here to upload</div>
<script type="text/javascript" src="js/slyupload.js"></script>
</body>
</html>
然后这是附加的 Javascript 文件:: 'js/slyupload.js'
<!-- begin snippet: -->
<!-- language: lang-js -->
// JavaScript Document
//ondragover, ondragleave...
(function(){
var dropzone = document.getElementById('dropzone');
var intv;
function displayUploads(data){
//console.log(data, data.length);
var uploads = document.getElementById('uploads'),anchor,x;
for(x=0; x < data.length; x++){
//alert(data[x].file);
anchor = document.createElement('a');
anchor.href = data[x].file;
anchor.innerText = data[x].name;
uploads.appendChild(anchor);
}
}
function upload(files){
//console.log(files);
var formData = new FormData(),
xhr = new XMLHttpRequest(), //for ajax calls...
x; //for the loop..
for(x=0;x<files.length; x++){
formData.append('file[]', files[x]);
/*//do this for every file...
xhr = new XMLHttpRequest();
//open... and send individually..
xhr.open('post', 'upload.php');
xhr.send(formData);*/
}
xhr.onload = function(){
var data = JSON.parse(this.responseText); //whatever comes from our php..
//console.log(data);
displayUploads(data);
//clear the interval when upload completes...
clearInterval(intv);
}
xhr.onerror = function(){
console.log(xhr.status);
}
//use this to send all together.. and disable the xhr sending above...
//open... and send individually..
intv = setInterval(updateProgress, 50);
xhr.open('post', 'upload.php');
xhr.send(formData);
//update progress...
/* */
}
function updateProgress(){
console.log('hello');
}
dropzone.ondrop = function(e){
e.preventDefault(); //prevent the default behaviour.. of displaying images when dropped...
this.className = 'dropzone';
//we can now call the uploading...
upload(e.dataTransfer.files); //the event has a data transfer object...
}
dropzone.ondragover = function(){
//console.log('hello');
this.className = 'dropzone dragover';
return false;
}
dropzone.ondragleave = function(){
this.className = 'dropzone';
return false;
}
}(window));
CSS:
正文{字体系列:Arial,Helvetica,无衬线;字体大小:12px; } .dropzone{ 宽度:300px;高度:300px;边框:2px 虚线 #ccc;颜色:#ccc;行高:300px;文本对齐:居中; } .dropzone.dragover{ 边框颜色:#000;颜色:#000; }
将 greedy : true
用作 droppable
的函数。然后仅启动在第一层上单击的事件。
:hover
样式或单击事件处理程序)。如果您想保留这些事件,这是我一直在使用的另一种解决方法:bensmithett.github.io/dragster.dropzone * {pointer-events: none;}
$('.element').addClass('dragging-over')
到您拖过的元素,然后$('.element').removeClass('dragging-over')
拖出。然后在你的 CSS 中你可以有.element.dragging-over * { pointer-events: none; }
。仅当您拖动时,才会从所有子元素中删除指针事件。