jQuery UI sortable drag initiation is slow when container has hidden items

As you can see on this jsferf example, calculating outerWidth()/outerHeight() (this is what the plugin does - see below) for hidden elements (with display none) is terribly slower than for visible elements, wether it is achieved by a style attribute or a class.

The only way I have found to bypass this and still achieve the same result is to set the height for the elements to hide to zero, instead of working with the display property, whether using the style atttibute or a class:

<li style="height: 0;">b</li>
<li class="hidden">b</li>

.hidden { height: 0 }

DEMO (with class) - DEMO (with style attr)


What's happenning with sortable when dragging an element ?

When starting dragging, the plugin refreshes the list of all items and recalculates positions of all elements. The plugin actually gets outerWidth and outerHeight:

_mouseStart: function(event, overrideHandle, noActivation) {
    ...
    //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
    this.refreshPositions();
    ...
}

refreshPositions: function(fast) {
    ...
    for (var i = this.items.length - 1; i >= 0; i--) {
        var item = this.items[i];
        ...
        if (!fast) {
            item.width = t.outerWidth();
            item.height = t.outerHeight();
        }
        var p = t.offset();
        item.left = p.left;
        item.top = p.top;
    };
    ...
    return this;
},​

If you still want to use display:none, this is a simple fix to the jQuery UI source specified in Didier's answer:

if (!fast) {
    if(item.item.css('display') === 'none') {
        item.width = 0;
        item.height = 0;
    }
    else {
        item.width = t.outerWidth();
        item.height = t.outerHeight();
    }
}

This is my very first post on stackoverflow, so do let me know if I messed something up.


I was also having a similar problem, but with hidden drop containers instead of sortable items. Here is my solution applying Jordan's answer to both sortable items and their containers and simply replacing the relvent method.

$.ui.sortable.prototype.refreshPositions = function(fast) {  
    //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
    if(this.offsetParent && this.helper) {
        this.offset.parent = this._getParentOffset();
    }

    for (var i = this.items.length - 1; i >= 0; i--){
        var item = this.items[i];

        //We ignore calculating positions of all connected containers when we're not over them
        if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
            continue;

        var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;

        if (!fast) {
            /********** MODIFICATION ***********/

            if(item.item.css('display') === 'none') {
                item.width = 0;
                item.height = 0;                    
            } else {
                item.width = t.outerWidth();
                item.height = t.outerHeight();
            }

            /********** END MODIFICATION ***********/
        }

        var p = t.offset();
        item.left = p.left;
        item.top = p.top;
    };

    if(this.options.custom && this.options.custom.refreshContainers) {
        this.options.custom.refreshContainers.call(this);
    } else {
        for (var i = this.containers.length - 1; i >= 0; i--){

            /********** MODIFICATION ***********/

            if (this.containers[i].element.css('display') == 'none') {
                this.containers[i].containerCache.left = 0;
                this.containers[i].containerCache.top = 0;
                this.containers[i].containerCache.width = 0;
                this.containers[i].containerCache.height = 0;
            } else {
                var p = this.containers[i].element.offset();
                this.containers[i].containerCache.left = p.left;
                this.containers[i].containerCache.top = p.top;
                this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
                this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
            }

            /********** END MODIFICATION ***********/
        };
    }

    return this;
};

Tags:

Jquery Ui