为 jQuery UI Droppable 的相交容差构建匹配选项

Posted

技术标签:

【中文标题】为 jQuery UI Droppable 的相交容差构建匹配选项【英文标题】:Build the matching option for jQuery UI Droppable's Intersect tolerance 【发布时间】:2018-03-13 04:13:54 【问题描述】:

我想将一个元素拖到两个或多个可放置区域中,但这些可放置区域需要完全包含在我的可拖动对象中。

问题是,jQuery UI's existing functionality for droppable tolerances 都没有满足这个需求。

理想情况下,我会使用“相交”,但可拖动和可放置对象的测量值在代码中是相反的。 (这个逻辑可以在jquery-ui.js搜索$.ui.intersect找到。)

我尝试用duck punching with jQuery 覆盖该函数,并尝试将tolerance 设置为这样的自定义函数:

tolerance: function(draggable, droppable) 
            if(!droppable.offset) return false;

            return ...logic check here...
        ,
        drop: ...continues...

都没有用。

这是一个 JSFiddle 来说明我的意思:https://jsfiddle.net/myingling/kgaqb0ay/5/

同样,应该分配一个人//覆盖// 的所有项目。

【问题讨论】:

【参考方案1】:

修改 $.ui.intersect 似乎是最好的方法。你有不同的选择。如果您不需要那么大的灵活性,您可以简单地添加一个 tolerance 类型,例如“cover”。然后,您只需要在开关中添加一个 case 来检查 intersect 中的公差类型,这可以恰好是“fit”的倒数。像这样:

  case 'fit':
    return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
    break;
  case 'cover':
    return (l >= x1 && x2 >= r && t >= y1 && y2 >= b);
    break;

见:https://jsfiddle.net/6nyqja4a/4/

或者,如果您想要更大的灵活性,您可以添加一个 tolerance 是函数的情况。然后你可以在选项中传递一个函数,它可以让你对不同的droppable有精确的容忍度。例如这样的事情: 在interserct函数中:

 if (toleranceMode instanceof Function) 

    return toleranceMode(draggable, droppable, x1, x2, y1, y2, l, r, t, b);

   else 
    switch (toleranceMode) 
      case 'fit':
        return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
        break;
...

你这样称呼它:

$('.droppable').droppable(
  hoverClass: "yellow",
  tolerance: function(drag, drop, x1, x2, y1, y2, l, r, t, b) 
    return (l >= x1 && x2 >= r && t >= y1 && y2 >= b);
  ,
  drop: function(event, ui) 
    $("#value_" + $(this).data("id")).val(ui.draggable.data("id"));
    console.log("Item " + $(this).data("id") + " taken by " + ui.draggable.data("id"));
  
);

见:https://jsfiddle.net/h4wm3r09/3/

从 jquery 1.12 $.ui.intersect 函数是作用域的,因此以后不能直接修改它。在$.ui.ddmanager中作为局部变量调用,所以即使修改$.ui.intersect也不会被使用。自定义它有点复杂。你可以这样做,基本上你重新调整 intersect 的范围,然后在 $.ui.ddmanager 上重新定义 dragdrop 方法strong> 以便调用修改后的 intersect:

var intersect = $.ui.intersect = ( function() 
    function isOverAxis( x, reference, size ) 
        return ( x >= reference ) && ( x < ( reference + size ) );
    

    return function( draggable, droppable, toleranceMode, event ) 

        if ( !droppable.offset ) 
            return false;
        

        var x1 = ( draggable.positionAbs ||
                draggable.position.absolute ).left + draggable.margins.left,
            y1 = ( draggable.positionAbs ||
                draggable.position.absolute ).top + draggable.margins.top,
            x2 = x1 + draggable.helperProportions.width,
            y2 = y1 + draggable.helperProportions.height,
            l = droppable.offset.left,
            t = droppable.offset.top,
            r = l + droppable.proportions().width,
            b = t + droppable.proportions().height;
        if (toleranceMode instanceof Function) 

            return toleranceMode(draggable, droppable, x1, x2, y1, y2, l, r, t, b);

         else 
            switch ( toleranceMode ) 
                case "fit":
                    return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );
                case "intersect":
                    return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half
                x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half
                t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half
                y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half
                case "pointer":
                    return isOverAxis( event.pageY, t, droppable.proportions().height ) &&
                isOverAxis( event.pageX, l, droppable.proportions().width );
                case "touch":
                    return (
                ( y1 >= t && y1 <= b ) || // Top edge touching
                ( y2 >= t && y2 <= b ) || // Bottom edge touching
                ( y1 < t && y2 > b ) // Surrounded vertically
            ) && (
                ( x1 >= l && x1 <= r ) || // Left edge touching
                ( x2 >= l && x2 <= r ) || // Right edge touching
                ( x1 < l && x2 > r ) // Surrounded horizontally
            );
                default:
                    return false;
            
        
    ;
 )();

然后,您无需更改任何内容,只需以完全相同的方式重新定义它们。

$.ui.ddmanager.drag = function( draggable, event ) 

    // If you have a highly dynamic page, you might try this option. It renders positions
    // every time you move the mouse.
    if ( draggable.options.refreshPositions ) 
        $.ui.ddmanager.prepareOffsets( draggable, event );
    

    // Run through all droppables and check their positions based on specific tolerance options
    $.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() 

        if ( this.options.disabled || this.greedyChild || !this.visible ) 
            return;
        

        var parentInstance, scope, parent,
            intersects = intersect( draggable, this, this.options.tolerance, event ),
            c = !intersects && this.isover ?
                "isout" :
                ( intersects && !this.isover ? "isover" : null );
        if ( !c ) 
            return;
        

        if ( this.options.greedy ) 

            // find droppable parents with same scope
            scope = this.options.scope;
            parent = this.element.parents( ":data(ui-droppable)" ).filter( function() 
                return $( this ).droppable( "instance" ).options.scope === scope;
             );

            if ( parent.length ) 
                parentInstance = $( parent[ 0 ] ).droppable( "instance" );
                parentInstance.greedyChild = ( c === "isover" );
            
        

        // We just moved into a greedy child
        if ( parentInstance && c === "isover" ) 
            parentInstance.isover = false;
            parentInstance.isout = true;
            parentInstance._out.call( parentInstance, event );
        

        this[ c ] = true;
        this[ c === "isout" ? "isover" : "isout" ] = false;
        this[ c === "isover" ? "_over" : "_out" ].call( this, event );

        // We just moved out of a greedy child
        if ( parentInstance && c === "isout" ) 
            parentInstance.isout = false;
            parentInstance.isover = true;
            parentInstance._over.call( parentInstance, event );
        
     );



$.ui.ddmanager.drop = function( draggable, event ) 

    var dropped = false;

    // Create a copy of the droppables in case the list changes during the drop (#9116)
    $.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() 

        if ( !this.options ) 
            return;
        
        if ( !this.options.disabled && this.visible &&
                intersect( draggable, this, this.options.tolerance, event ) ) 
            dropped = this._drop.call( this, event ) || dropped;
        

        if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ],
                ( draggable.currentItem || draggable.element ) ) ) 
            this.isout = true;
            this.isover = false;
            this._deactivate.call( this, event );
        

     );
    return dropped;


https://jsfiddle.net/u6wfj8mj/1/

显然,这个更容易出错,可能有更好的方法来实现这一点。例如,通常您可以扩展小部件,这样会更干净。但是 intersectddmanagerdraggabledroppable 中都使用,并且不直接在这些小部件中。所以更难以干净的方式扩展。 您也可以将逻辑直接放在可拖动和可放置对象的拖动事件和放置事件中,但由于存在默认容差,因此不确定它是否更好。

【讨论】:

这在小提琴中看起来很棒,谢谢!但我遇到了实现问题。为什么它在这里不起作用? myingling.com/random/random/46519613 @Bing 是因为 jquery ui 的版本问题。在小提琴中你有 1.9,在你的例子中你有 1.12。我已编辑为 1.12 添加解决方案。 成功了,谢谢!出于我自己的研究目的:是否有参考 jQuery/jQuery UI 版本之间的主要区别?我很惊讶 1.9 -> 1.12 会改变这一点。 有升级指南(例如:jqueryui.com/upgrade-guide/1.12),但没有提及结构的变化。我猜他们正试图使其更模块化和更健壮(这也意味着在这种情况下更难定制)。在许多情况下,这种结构具有优势。【参考方案2】:

如何让 jQuery UI 的 Droppable 在其左上角进入时接受 Draggable? 我为 jQuery UI Droppable 1.9.2 创建了 jquery.ui.droppable-patch.js

(function ($, undefined) 
$.ui.intersect = function (draggable, droppable, toleranceMode) 
    if (!droppable.offset) return false;
    var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
        y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height;
    var l = droppable.offset.left, r = l + droppable.proportions.width,
        t = droppable.offset.top, b = t + droppable.proportions.height;
    switch (toleranceMode) 
        case 'fit':
            return (l <= x1 && x2 <= r
            && t <= y1 && y2 <= b);
            break;
        case 'intersect':
            return (l < x1 + (draggable.helperProportions.width / 2) // Right Half
            && x2 - (draggable.helperProportions.width / 2) < r // Left Half
            && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half
            && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
            break;
        case 'pointer':
            var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left),
                draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top),
                isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width);
            return isOver;
            break;
        case 'touch':
            return (
                    (y1 >= t && y1 <= b) || // Top edge touching
                    (y2 >= t && y2 <= b) || // Bottom edge touching
                    (y1 < t && y2 > b)      // Surrounded vertically
                ) && (
                    (x1 >= l && x1 <= r) || // Left edge touching
                    (x2 >= l && x2 <= r) || // Right edge touching
                    (x1 < l && x2 > r)      // Surrounded horizontally
                );
            break;

        case "top-left-touch":
            return ( y1 >= t && y1 <= b ) && ( x1 >= l && x1 <= r );
        case "top-right-touch":
            return ( y1 >= t && y1 <= b ) && ( x2 >= l && x2 <= r );
        case "bottom-left-touch":
            return ( y2 >= t && y2 <= b ) && ( x1 >= l && x1 <= r );
        case "bottom-right-touch":
            return ( y2 >= t && y2 <= b ) && ( x2 >= l && x2 <= r );
        default:
            return false;
            break;
    
;

(jQuery));

【讨论】:

以上是关于为 jQuery UI Droppable 的相交容差构建匹配选项的主要内容,如果未能解决你的问题,请参考以下文章

JQuery UI:在 Droppable Drop 时取消排序

jquery-ui,如何将光标恢复为默认值

Jquery UI Droppable revert不能再次删除

02 Jquery UI Droppable 放置插件

使用 jquery-ui droppable 时,如何从 droppable 区域中删除已经删除的项目?

jQuery UI (Droppable):如果 droppable 具有相对/绝对的 css 位置,则可拖动元素不会放置在鼠标指针处