Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sortable behaves wrong when CSS3 scale is applied

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/

like image 643
Adrian Rosca Avatar asked Jul 26 '14 12:07

Adrian Rosca


People also ask

What is scale() in CSS?

The scale() CSS function defines a transformation that resizes an element on the 2D plane. Because the amount of scaling is defined by a vector, it can resize the horizontal and vertical dimensions at different scales. Its result is a <transform-function> data type.

How does jquery sortable work?

jQueryUI provides sortable() method to reorder elements in list or grid using the mouse. This method performs sortability action based upon an operation string passed as the first parameter.

What is UI sortable?

jQueryUI sortable() method is used to re-order elements in the list or grid form, by using the mouse. The sorting ability of this method is based on an operation string passed as the first parameter.


2 Answers

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/

like image 146
Julien Grégoire Avatar answered Sep 19 '22 07:09

Julien Grégoire


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/

like image 20
Windian Avatar answered Sep 21 '22 07:09

Windian