jQuery可拖动和可滚动的div元素

10

我的一个小问题与 jQuery UI 的 droppable 组件有关,但我不确定这个问题是由于我的代码还是组件中的bug造成的。

我有一个固定宽度和高度的 div。该div的 overflow-x 设置为隐藏,overflow-y 设置为自动。 在该div内部,我有一些更多的div。有许多这样的div,以至于外部div开始滚动。每个内部div都是可拖拽的,可以接受一个在包装器div之外的可拖拽元素。

如果我将可拖拽项拖放到包装器内的某个位置,一切都很正常。问题在于,即使我将元素短暂地放在包装器div下面,drop事件也会被触发。

我不太擅长解释这个问题;因此,这里有一些可以重现问题的代码:

http://jsfiddle.net/2p56Y/

只需将“拖动我!”容器拖到带滚动条的div下面。意外的是,你会看到弹出 "dropped" 。

现在有趣的事情来了:如果您向下滚动到项目 "Test28",现在您将可拖动的项拖放到包装器下面,drop事件将不会被触发。看起来,当你在隐藏元素上放下某些东西时,它们仍然是可以访问的。

那么,这是一个bug还是我需要以不同的方式编写我的代码才能使它工作?(或者两者都要?:-))


2
看起来一个可能的解决方法是在“放置”事件的函数中添加这个条件:如果 ($(this).position().top < $('.box').height()),请参见 http://jsfiddle.net/5NuLa/2/。 - StefanS
@StefanS:对我来说,这似乎是一个合理的修复。 - Andrew Whitaker
@StefanS 如果您在Chrome中检查DOM元素,您将更好地了解当设置溢出属性时浏览器内部发生的情况。不幸的是,由于元素仍然被渲染和定位,但被遮挡,因此事件仍将触发...即它对DOM是“可见”的。您需要编写代码来检查拖放位置是否超出父容器边界。我会尝试一下并看看结果如何。 - lsuarez
@lthibodeaux 这正是我所想的。遗憾的是,没有一个过滤器可以真正告诉你一个元素是否可见。:visible在某些情况下能胜任,但在这种特定情况下不行。也许可以扩展droppable.js的intersect函数以涵盖此情况?https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.droppable.js - StefanS
你的修复只适用于底部,但实际上你也需要顶部(如果你有x-scrolling,则两侧也需要),这使得修复变得有点丑陋。请参见http://jsfiddle.net/5NuLa/3/,将可滚动的div滚动到一半,然后将可投放的内容放在div上方,同样的问题会发生,因为你只修复了底部。 - davin
显示剩余3条评论
3个回答

3
检查可放置元素相对于父容器的边界,并在可放置元素的底部高于父容器的顶部或可放置元素的顶部低于父容器的底部时中断函数的执行:
$('.item').droppable( {
    activeClass: "ui-state-default",
    hoverClass: "ui-state-hover",
    accept: "#draggable",
    drop: function( event, ui ) {
        var cTop = $(this).closest(".box").position().top + parseInt($(this).closest(".box").css("margin-top"));
        var cBtm = $(this).closest(".box").position().top + parseInt($(this).closest(".box").css("margin-top")) + $(this).closest(".box").height();
        var dTop = $(this).position().top + parseInt($(this).css("margin-top"));
        var dBtm = $(this).position().top + parseInt($(this).css("margin-top")) + $(this).height()

        if (dBtm > cTop && dTop < cBtm) {
            alert("Dropped.");
        }
    }
});

示例:http://jsfiddle.net/lthibodeaux/2p56Y/6/

我知道这并不优雅,但它似乎可行。我承认只对此脚本进行了粗略测试。


你应该让它更加灵活一些,如果可放置元素在DOM中再往下一层,这将会导致出错。我建议在相关元素上添加一个简单的.data,引用适当的“父”元素,比如我们的情况下是 $('.box') - davin
谢谢,这对我来说似乎可行,并且比我的检查更通用 :-) - StefanS
@davin 我会把这个交给 OP。这只是一个概念,而不是通用解决方案。可以很容易地将 parent() 更改为 closest(".box")。编辑:灵活的。 - lsuarez

3

虽然我认为你已经接受了一个答案,但我还是想提供一个更优雅的解决方案(基于事件冒泡,并且只处理一级可见性,不进行递归)。你可以通过将$('.box, .item').droppable()设置为默认值greedy:false来实现,这样嵌套的div的放置事件应该会触发,然后是外部div的放置事件。

hasClass('box')这样的简单元素检查意味着放置发生在有效区域内,所以你需要在内部放置事件中缓存被放置的元素,然后在外部div的放置事件中(正如前面提到的)稍后做任何操作。

如果你在外部div之外放置,即使内部div的放置事件会触发,外部div也不会触发,因此除了无用的缓存事件之外,什么都没有发生。唯一的问题是非贪婪嵌套可放置元素似乎存在一个bug,jQuery示例http://jqueryui.com/demos/droppable/propagation.html对我来说甚至不能正常工作-它表现得好像正在使用事件捕获而不是事件冒泡...

唯一其他的(诚然更有可能的)解释是我误解了嵌套可放置元素的行为方式。


在我看来,它相当聪明。 - lsuarez
这个想法不错,但是浏览器似乎仍然将溢出的 div 视为被遮挡的内容仍然是可放置区域的一部分。我还没有能够应用这个想法。 :[ - lsuarez

0

最近刚好解决了这个问题,所以我想在这里分享一下。

首先要提到的是,涉及到嵌套可拖拽元素和事件冒泡的类似问题可以在这里找到解决方案。但以下解决方案适用于不嵌套但有重叠的可拖拽元素。

问题(技术术语)

如果您根据 OP 的 jsfiddle 使用他的指示进行查看,则会注意到这两个 DOM 元素彼此独立且位于同一 DOM“层级”上。然而,正如 OP 和其他几个人所注意到的那样,jQuery UI 的 Droppable 似乎将放置事件冒泡到具有溢出内容的 DOM 元素,而这些元素 下覆 目标可拖拽元素。

解决方案

在目标可拖拽元素中放置元素时设置一个标志。在下覆 DOM 元素的放置事件中检查该标志。如果该标志包含指示已在目标可拖拽元素中放置的值,则返回 false、不执行任何操作或还原操作。无论您希望忽略放置事件执行什么操作。

对于我的特定情况,我通过在目标可拖拽元素的下降事件中对 ui.helper 设置一个简单的属性值来设置我的标志,代码如下:

$(ui.helper).attr('ignore-drop-event', "true");

在下层可放置元素的气泡状拖放事件中,我会检查此属性是否设置为 true:
if ($(ui.helper).attr('ignore-drop-event') === "true") {
    // Ignore this drop event since it occurred as part of event bubbling
}
else {
    // This is a "real" drop event
}

这个解决方案基于以下假设:

  1. 事件冒泡顺序是一致的。 这意味着目标可放置元素将始终在重叠的可放置元素之前接收事件。我不知道这是否总是正确的,但在我的测试中,这种评估是准确的。

  2. 属性值在冒泡的放置事件上的标志检查之前被定义。

希望这能帮助其他人。


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