Sortable behaves wrong when CSS3 scale is applied

Problem

I'm scaling a JQuery sortable element with CSS transform. Both the sortable items start positions and offsets while dragging are wrong beacuse JQuery doesn't take CSS scales into consideration. I solved it partially with code found here:

jQuery Drag/Resize with CSS Transform Scale

But the thing I cannot solve is the sortable item position at drag starts. It jumps up and right a bit. I can't figure out what to put into the start event handler:

        start: function(e, ui)
        {
            // something is needed here to fix the initial offset
        }

This Fiddle shows the problem: http://jsfiddle.net/adrianrosca/zbvLp/4/

Problem courtesy of: Adrian Rosca

Solution

One difference with draggable is that the transform is not on the elements themselves, but on the parent. So it changes a bit the logic.

Here's a solution for this specific case, but you'll see that depending on the situation it may change. For example, if you change transform-origin, or if you have an horizontal sortable, it'll have to be adapted. But the logic stays the same:

var zoomScale = 0.5;

$(".container")
  .sortable({

    sort: function(e, ui) {
    console.log(ui.position.left)
      var changeLeft = ui.position.left - ui.originalPosition.left;
      // For left position, the problem here is not only the scaling,
      // but the transform origin. Since the position is dynamic
      // the left coordinate you get from ui.position is not the one
      // used by transform origin. You need to adjust so that
      // it stays "0", this way the transform will replace it properly
      var newLeft = ui.originalPosition.left + changeLeft / zoomScale - ui.item.parent().offset().left;

      // For top, it's simpler. Since origin is top, 
      // no need to adjust the offset. Simply undo the correction
      // on the position that transform is doing so that
      // it stays with the mouse position
      var newTop = ui.position.top / zoomScale;

      ui.helper.css({
        left: newLeft,
        top: newTop
      });
    }
  });

http://jsfiddle.net/aL4ntzsh/5/

EDIT:

Previous answer will work for positioning, but as pointed by Decent Dabbler, there is a flaw with the intersection function that validates when a sorting should occur. Basically, positions are correctly calculated, but the items keep width and height values that are not transformed, which is causing the problem. You can adjust these values by modifying them on start event to take scale factor into account. Like this for example:

 start: function(e, ui) {
      var items = $(this).data()['ui-sortable'].items;
      items.forEach(function(item) {
        item.height *= zoomScale;
        item.width *= zoomScale;
      });
    }

http://jsfiddle.net/rgxnup4v/2/

Solution courtesy of: Julien Grégoire

Discussion

There are two things in your problem:

  1. Position property of a element is calculated based on nearest parent which has position is absolute or relative. Right now based parent is body which is not good so you have to add position:relative; for container.
  2. Because, as you already notice, JQuery doesn't take CSS scales into consideration when calculate both ui.position and ui.originalPosition so you have to "apply" zoomScale to both of them.

CSS

.container
{
    position: relative;
    transform: scale(0.5);
    transform-origin: center top;
}

Script

var changeLeft = ui.position.left - ui.originalPosition.left;
var newLeft = (ui.originalPosition.left + changeLeft) / zoomScale;
var changeTop = ui.position.top - ui.originalPosition.top;
var newTop = (ui.originalPosition.top + changeTop) / zoomScale;

This is update code: http://jsfiddle.net/zbvLp/9/

Discussion courtesy of: user3765122

This recipe can be found in it's original form on Stack Over Flow.