50

I am applying a CSS transform (and the browser specific -webkit, -o etc):

transform: matrix(0.5 , 0 , 0, 0.5, 0 , 0);

to a div then using jQuery's draggable() and resizable() plugins on children of said div.

The problem I had was that when dragging or resizing the child elements, the alteration jQuery made were out of "sync" with the mouse by a factor equal to the scale applied.

I found a solution on stackoverflow (though I stupidly did not bookmark it and now cant find it....) that suggested patching the plugins, and it worked wonderfully. It went along these line:

function monkeyPatch_mouseStart() {
  // don't really need this, but in case I did, I could store it and chain
  // var oldFn = $.ui.draggable.prototype._mouseStart ;
  $.ui.draggable.prototype._mouseStart = function(event) {

    var o = this.options;

    //Create and append the visible helper
    this.helper = this._createHelper(event);

    //Cache the helper size
    this._cacheHelperProportions();

    //If ddmanager is used for droppables, set the global draggable
    if($.ui.ddmanager)
      $.ui.ddmanager.current = this;

    /*
     * - Position generation -
     * This block generates everything position related - it's the core of draggables.
     */

    //Cache the margins of the original element
    this._cacheMargins();

    //Store the helper's css position
    this.cssPosition = this.helper.css("position");
    this.scrollParent = this.helper.scrollParent();

    //The element's absolute position on the page minus margins

    //PATCH CODE
    this.offset = this.positionAbs = getViewOffset(this.element[0]);
    //END

    this.offset = {
      top: this.offset.top - this.margins.top,
      left: this.offset.left - this.margins.left
    };

    $.extend(this.offset, {
      click: { //Where the click happened, relative to the element
        left: event.pageX - this.offset.left,
        top: event.pageY - this.offset.top
      },
      parent: this._getParentOffset(),
      relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
    });

    //Generate the original position
    this.originalPosition = this.position = this._generatePosition(event);
    this.originalPageX = event.pageX;
    this.originalPageY = event.pageY;

    //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
    if(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt)){
    }

    //Set a containment if given in the options
    if(o.containment)
      this._setContainment();

    //Trigger event + callbacks
    if(this._trigger("start", event) === false) {
      this._clear();
      return false;
    }

    //Recache the helper size
    this._cacheHelperProportions();

    //Prepare the droppable offsets
    if ($.ui.ddmanager && !o.dropBehaviour)
      $.ui.ddmanager.prepareOffsets(this, event);

    this.helper.addClass("ui-draggable-dragging");
    this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position

    //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
    if ( $.ui.ddmanager && $.ui.ddmanager.dragStart) $.ui.ddmanager.dragStart(this, event);

    return true;
  }
 }

function getViewOffset(node) {
  var x = 0, y = 0, win = node.ownerDocument.defaultView || window;
  if (node) addOffset(node);
  return { left: x, top: y };

  function getStyle(node) {
    return node.currentStyle || // IE
           win.getComputedStyle(node, '');
  }

  function addOffset(node) {
    var p = node.offsetParent, style, X, Y;
    x += parseInt(node.offsetLeft, 10) || 0;
    y += parseInt(node.offsetTop, 10) || 0;

    if (p) {
      x -= parseInt(p.scrollLeft, 10) || 0;
      y -= parseInt(p.scrollTop, 10) || 0;

      if (p.nodeType == 1) {
        var parentStyle = getStyle(p)
          , localName   = p.localName
          , parent      = node.parentNode;
        if (parentStyle.position != 'static') {
          x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
          y += parseInt(parentStyle.borderTopWidth, 10) || 0;

          if (localName == 'TABLE') {
            x += parseInt(parentStyle.paddingLeft, 10) || 0;
            y += parseInt(parentStyle.paddingTop, 10) || 0;
          }
          else if (localName == 'BODY') {
            style = getStyle(node);
            x += parseInt(style.marginLeft, 10) || 0;
            y += parseInt(style.marginTop, 10) || 0;
          }
        }
        else if (localName == 'BODY') {
          x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
          y += parseInt(parentStyle.borderTopWidth, 10) || 0;
        }

        while (p != parent) {
          x -= parseInt(parent.scrollLeft, 10) || 0;
          y -= parseInt(parent.scrollTop, 10) || 0;
          parent = parent.parentNode;
        }
        addOffset(p);
      }
    }
    else {
      if (node.localName == 'BODY') {
        style = getStyle(node);
        x += parseInt(style.borderLeftWidth, 10) || 0;
        y += parseInt(style.borderTopWidth, 10) || 0;

        var htmlStyle = getStyle(node.parentNode);
        x -= parseInt(htmlStyle.paddingLeft, 10) || 0;
        y -= parseInt(htmlStyle.paddingTop, 10) || 0;
      }

      if ((X = node.scrollLeft)) x += parseInt(X, 10) || 0;
      if ((Y = node.scrollTop))  y += parseInt(Y, 10) || 0;
    }
  }
}
var isNumber = function(value) {
  return !isNaN(parseInt(value, 10));
};

I have made my own changes such as (you can see on the 6-7 lines the multiplication of the movement by a "scale factor"):

 $.ui.draggable.prototype._generatePosition = function(event) {
    var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
    var pageX = event.pageX;
    var pageY = event.pageY;
    //PATCH CODE
    if($(this.element[0]).hasClass('item')){
        pageY = this.originalPageY + ((pageY - this.originalPageY)*(1/$.viewbox.foreground.scale));
        pageX = this.originalPageX + ((pageX - this.originalPageX)*(1/$.viewbox.foreground.scale));
    }
    //END
    /*
     * - Position constraining -
     * Constrain the position to a mix of grid, containment.
     */

    if(this.originalPosition) { //If we are not dragging yet, we won't check for options

      if(this.containment) {
        if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
        if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
        if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
        if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
      }

      if(o.grid) {
        var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
        pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

        var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
        pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
      }
    }
    return {
      top: (
        pageY                               // The absolute mouse position
        - this.offset.click.top                         // Click offset (relative to the element)
        - this.offset.relative.top                        // Only for relative positioned nodes: Relative offset from element to offset parent
        - this.offset.parent.top                        // The offsetParent's offset without borders (offset + border)
        + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
      ),
      left: (
        pageX                               // The absolute mouse position
        - this.offset.click.left                        // Click offset (relative to the element)
        - this.offset.relative.left                       // Only for relative positioned nodes: Relative offset from element to offset parent
        - this.offset.parent.left                       // The offsetParent's offset without borders (offset + border)
        + ($.browser.safari && $.browser.version < 526 && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
      )
    };

  }

So big thank you to whoever suggested that.

So, my question! Has anyone come across a nice way to have draggable/resizable events inside a scaled element that doesn't require patching jQuery? I have googled, and this was the best solution I could find. Does anyone know of alternative to jquery that perhaps works under these conditions, with CSS transforms?

Many thanks for any responses.

paullth
  • 1,731
  • 2
  • 15
  • 19
  • 2
    I'm not certain it's 100% relevant but since you posted this question, I answered something not completely dissimilar [here](http://stackoverflow.com/questions/10456234/resizing-a-jquery-draggable-elements-containment-parent-while-dragging/10458037#10458037). The solution on that occasion was to provide a `drag` callback to impose custom containment in place of draggable's native containment behaviour, which was defeated by dynamic resizing of the container. – Beetroot-Beetroot May 23 '12 at 21:21
  • I see what you mean, it is a similar situation, the parent/containment element is being manipulated in a fashion jquery is not checking for (as you wouldnt expect it to). The callbacks are a good idea, I will keep that in mind for next time. In the end I wrote a jquery ui widget factory extension that was specific to what I was doing. If I have some time I will clean it of my clients code an put it up here. – paullth May 26 '12 at 18:06
  • Report the bug to http://bugs.jquery.com/newticket?redirectedfrom=http://stackoverflow.com/questions/10212683/jquery-drag-resize-with-css-transform-scale – Imdad Jun 28 '12 at 13:13
  • 2
    Imdad, that's not a bug to begin with, it's a lack of a feature that isn't needed for the vast majority of developers, implementing it would be a waste of performance for many. Anyways, I don't have a full solution, but I've got an events script which you're welcome to adapt (might be easier for you): http://extraordinarythoughts.com/2012/02/13/jquery-multi-touch-getting-started/ – Shawn Khameneh Jun 28 '12 at 19:26
  • @Shawn that script looks useful, will probably adapt as you say at some point. thanks – paullth Jun 29 '12 at 16:54
  • @paullth can you explain please how can I use this code? Thanks!! – chemitaxis May 23 '14 at 07:02
  • @chemitaxis you would just include the whole lot in your js, then call monkeyPatch_mouseStart() before doing anything. (note: this was 2 years ago and I don't really remember) – paullth May 23 '14 at 09:27

13 Answers13

46

It's been a while since this question was asked. I have found (actually created) an answer. All it requires is setting callback handlers. No editing jquery-ui needed!

Note: zoomScale in this example is a global variable and the transform is set using animate (aided by jquery.transform.js) like so:

target.animate({
    transform: 'scale(' + zoomScale + ')'
});

Take a look at this:

transform scale() fix for resizable:

$(this).resizable({
    minWidth: -(contentElem.width()) * 10,  // these need to be large and negative
    minHeight: -(contentElem.height()) * 10, // so we can shrink our resizable while scaled
    resize: function(event, ui) {

        var changeWidth = ui.size.width - ui.originalSize.width; // find change in width
        var newWidth = ui.originalSize.width + changeWidth / zoomScale; // adjust new width by our zoomScale

        var changeHeight = ui.size.height - ui.originalSize.height; // find change in height
        var newHeight = ui.originalSize.height + changeHeight / zoomScale; // adjust new height by our zoomScale

        ui.size.width = newWidth;
        ui.size.height = newHeight;

    }
});

transform scale() fix for draggable:

$(this).draggable({
    handle: '.drag-handle',
    start: function(event, ui) {
        ui.position.left = 0;
        ui.position.top = 0;
    },
    drag: function(event, ui) {

        var changeLeft = ui.position.left - ui.originalPosition.left; // find change in left
        var newLeft = ui.originalPosition.left + changeLeft / (( zoomScale)); // adjust new left by our zoomScale

        var changeTop = ui.position.top - ui.originalPosition.top; // find change in top
        var newTop = ui.originalPosition.top + changeTop / zoomScale; // adjust new top by our zoomScale

        ui.position.left = newLeft;
        ui.position.top = newTop;

    }
});

Let me know if you find any problems or further improvements on this. :)

Reference: jQuery-UI resizable/draggable with transform: scale() set

Gung Foo
  • 13,392
  • 5
  • 31
  • 39
  • Using the start handle breaks the drag handle for me. If I don't use it, there's an initial offset, but at least dragging works. – naugtur Feb 18 '13 at 15:00
  • please supply an example and provide which browser you are using! It could be that the code errs because i am using `.drag-handle` see the handle parameter and this you might not have in your code. try using just `$(this)` instead of `$('.drag-handle',this)` – Gung Foo Feb 18 '13 at 18:03
  • I see you fixed the drag handle, but I got it fixed right away. The problem is that with latest jqueryui overwriting `ui.position` in `start` breaks the proportions in equation inside `drag` and coords get negative values. I don't have an isolated testcase at the moment, but I think I'll have to prepare one anyway – naugtur Feb 18 '13 at 18:50
  • the above solution works for me! i suspect the difference might lie in the fact that for me the parent of the draggable is scaled also. if only the draggable is scaled, transform-origin should suffice: https://developer.mozilla.org/en-US/docs/CSS/transform-origin – Gung Foo Feb 19 '13 at 18:03
  • I'm scaling only the container for draggables. the `start` part doesn't work for me at all. But I was able to add this functionality to jqueryui without any performance impact and I'll be trying to push it upstream. It needs a bit of polishing before I do. I'm working on it as my day job this week, so I hope I'll post it soon. – naugtur Feb 19 '13 at 20:09
  • there is already a bugreport at jquery-ui about this regarding only the draggable being scaled. seems the guy managed to fix it without any need to change the source. he simply added transform-origin! – Gung Foo Feb 19 '13 at 23:07
  • Please link. It's still not a fix for my situation, but interesting – naugtur Feb 20 '13 at 09:10
  • http://bugs.jqueryui.com/ticket/7865 i could verify that this was working if only the draggable element is being scaled – Gung Foo Feb 20 '13 at 10:59
  • 1
    Everything is working great for me since I am also making a container that is getting transformed. My only glitch is that I am getting a little jump upwards and to the eft when starting to resize. Any clues? I am using $(this) instead of contentElement. Is that the problem? – Doug Wolfgram Sep 23 '13 at 03:04
  • did you also rotate the element? ..rotation was the only thing i had to account for with the resize fix. (the $(this) should be fine), the minWidth and minHeight are parameters for the call to .resizable(); – Gung Foo Sep 23 '13 at 07:19
  • Just FYI, this solution doesn't like it if the `containment` object is also scaled. – user1618143 Dec 02 '13 at 17:05
  • @DougWolfgram I'm also scaling the container and the start position doesn't work for me either. Did you find a solution? – Adrian Rosca Jul 26 '14 at 10:38
  • This solution works when the `containment` is scaled: http://cerdiogenes.blogspot.com.br/2015/01/jquery-ui-draggable-reseizable-with.html – Kadu Diógenes Jan 09 '15 at 00:47
  • If you use the tolerance option(at least when set to `pointer`) on Droppable, you do need to tweak the UI source a bit. `case "pointer": return e(n.pageY, u, i.proportions().height) && e(n.pageX, l, i.proportions().width);` to `case "pointer":return e(n.pageY, u, i.proportions().height*zoomScale) && e(n.pageX, l, i.proportions().width*zoomScale);` – aksu Jan 11 '16 at 16:08
  • 1
    In "resize" you need to do same for **ui.position.left** and **ui.position.top** – Ivan Gusev Jun 17 '16 at 15:48
  • @GungFoo please see this http://jsfiddle.net/dfqtt/90/ . This is your solution. Why it's not working? – n.y Sep 18 '16 at 08:11
  • @NaserYousefi because the solution depends on the scale transform being applied to the resizable's parent. – Gung Foo Sep 23 '16 at 07:59
  • isn't `ui.size.height` the same as `ui.originalSize.height + changeHeight`. why not just devide `ui.size.height ` by `zoomScale` – Lonergan6275 Jul 28 '17 at 08:51
  • The drag callback worked for me but I had to make a couple of changes to the newLeft and newTop calculations. I added some parenthesis to get the order of operations correct. In the example the zoomScale is only applied to the change value not the new position. `var changeLeft = ui.position.left - ui.originalPosition.left, newLeft = ( ui.originalPosition.left + changeLeft ) / zoomScale , changeTop = ui.position.top - ui.originalPosition.top, newTop = ( ui.originalPosition.top + changeTop ) / zoomScale;` – Lorenzo Gangi Dec 29 '17 at 19:12
  • Thank you for the solution! But I found resizable not fully work. Problem is the ui element already resized using the wrong size, so the preview still shows wrong. To fix this, add the following below the resizable fix code: var elementStyle = ui.element.context.style; elementStyle.width = newWidth + 'px'; elementStyle.height = newHeight + 'px'; – Antony Ng Aug 16 '18 at 02:03
4

I was trying the transform scale() fix for resizable posted by gungfoo on a element displayed at 10% of its actual size and the method didin't work. The cursor still moved away from the element during resizing.

I changed the last two lines of the resizeFix method to directly update the element's width and height and this solved my issue.

function resizeFix(event, ui) {

    var changeWidth = ui.size.width - ui.originalSize.width; // find change in width
    var newWidth = ui.originalSize.width + changeWidth / zoomScale; // adjust new width by our zoomScale

    var changeHeight = ui.size.height - ui.originalSize.height; // find change in height
    var newHeight = ui.originalSize.height + changeHeight / zoomScale; // adjust new height by our zoomScale

    ui.originalElement.width(newWidth);
    ui.originalElement.height(newHeight);
}
  • This answer is extremely helpful when it comes to aspect ratio locking! I'm unsure what JQuery UI is doing under the hood with the wrapped ui.size / ui.position but the only way to make locked aspect ratio work while also applying scaling was to use this approach. Along with the top and left calculation, set using ui.originalElement.css('top', newTop) and ui.originalElement.css('left', newLeft). Thanks! – Nick Hanshaw Nov 05 '20 at 21:02
3

i had a similar problem with transformation and ended up solving it with css:

transform-origin

http://www.w3schools.com/cssref/css3_pr_transform-origin.asp

have you tried it? maybe it will help.

Guy
  • 12,488
  • 16
  • 79
  • 119
  • I have that set to 0,0 and changing it only affects the point of origin of the transform. Thanks – paullth Jul 15 '12 at 20:02
3

My own "answer" was on this occasion was to adapt jQuery UI draggable to make a separate interaction, called "traggable".

https://github.com/paullth/JS-libs

http://jsfiddle.net/paul_herve/ws27C/4/

Would still like to hear about any alternatives...

paullth
  • 1,731
  • 2
  • 15
  • 19
  • I had a similar issue with dragging transformed elements, Paul's `traggable` script worked perfectly. Far better than the `monkeyPatch_mouseStart` hack. Thanks Paul. – Ben Smith Nov 25 '12 at 15:23
  • 1
    I created (since i had to) a solution based on callbacks for both resizable and draggable, have a look! – Gung Foo Feb 15 '13 at 08:26
  • @GungFoo Im going to accept your answer, it best matches the question in that it doesn't require rewriting any jQuery or developing your own plugin. Nice one – paullth Feb 15 '13 at 08:28
  • Not having to rewrite jqueryui was my reason to come up with this. I'm glad if it helped someone :) – Gung Foo Feb 15 '13 at 08:33
2

Another approach would be to add a plugin that compensate for the transformation ( remember to add "transform : true" to the plugin initialization.

The ui.draggable need to be passed via a inverse matrix of the transformation in order to position the element in the un-transformed space which the browser later transform on display.

For "draggable" the following worked for me ( jqueryui 1.10 ) ( the matrix calculation I have taken from jquery.panzoom):

var Matrix = function(a, b, c, d, e, f, g, h, i) {
    if ($.type(a) === 'array') {
        this.elements = [
            +a[0], +a[2], +a[4],
            +a[1], +a[3], +a[5],
                0,     0,     1
        ];
    } else {
        this.elements = [
            a, b, c,
            d, e, f,
            g || 0, h || 0, i || 1
        ];
    }
};

Matrix.prototype = {
    /**
     * Multiply a 3x3 matrix by a similar matrix or a vector
     * @param {Matrix|Vector} matrix
     * @return {Matrix|Vector} Returns a vector if multiplying by a vector
     */
    x: function(matrix) {
        var isVector = matrix instanceof Vector;

        var a = this.elements,
            b = matrix.elements;

        if (isVector && b.length === 3) {
            // b is actually a vector
            return new Vector(
                a[0] * b[0] + a[1] * b[1] + a[2] * b[2],
                a[3] * b[0] + a[4] * b[1] + a[5] * b[2],
                a[6] * b[0] + a[7] * b[1] + a[8] * b[2]
            );
        } else if (b.length === a.length) {
            // b is a 3x3 matrix
            return new Matrix(
                a[0] * b[0] + a[1] * b[3] + a[2] * b[6],
                a[0] * b[1] + a[1] * b[4] + a[2] * b[7],
                a[0] * b[2] + a[1] * b[5] + a[2] * b[8],

                a[3] * b[0] + a[4] * b[3] + a[5] * b[6],
                a[3] * b[1] + a[4] * b[4] + a[5] * b[7],
                a[3] * b[2] + a[4] * b[5] + a[5] * b[8],

                a[6] * b[0] + a[7] * b[3] + a[8] * b[6],
                a[6] * b[1] + a[7] * b[4] + a[8] * b[7],
                a[6] * b[2] + a[7] * b[5] + a[8] * b[8]
            );
        }
        return false; // fail
    },
    /**
     * Generates an inverse of the current matrix
     * @returns {Matrix}
     */
    inverse: function() {
        var d = 1 / this.determinant(),
            a = this.elements;
        return new Matrix(
            d * ( a[8] * a[4] - a[7] * a[5]),
            d * (-(a[8] * a[1] - a[7] * a[2])),
            d * ( a[5] * a[1] - a[4] * a[2]),

            d * (-(a[8] * a[3] - a[6] * a[5])),
            d * ( a[8] * a[0] - a[6] * a[2]),
            d * (-(a[5] * a[0] - a[3] * a[2])),

            d * ( a[7] * a[3] - a[6] * a[4]),
            d * (-(a[7] * a[0] - a[6] * a[1])),
            d * ( a[4] * a[0] - a[3] * a[1])
        );
    },
    /**
     * Calculates the determinant of the current matrix
     * @returns {Number}
     */
    determinant: function() {
        var a = this.elements;
        return a[0] * (a[8] * a[4] - a[7] * a[5]) - a[3] * (a[8] * a[1] - a[7] * a[2]) + a[6] * (a[5] * a[1] - a[4] * a[2]);
    }
};

var Vector = function (x, y, z) {
    this.elements = [ x, y, z ];
};

/**
 * Get the element at zero-indexed index i
 * @param {Number} i
 */
Vector.prototype.e = Matrix.prototype.e = function(i) {

    if( this.elements[ i ] != undefined ){
        return this.elements[ i ];    
    }

    return this.elements;
};    

$.ui.plugin.add("draggable", "transform", {

    start: function( event, ui ) {

        if(!$(this).data('ui-draggable')){
            return false;
        }            

        var inst = $(this).data("ui-draggable");

        inst.matrix = new Matrix(function(matrix){

            var rmatrix = new RegExp(
                    '^matrix\\(' +
                    '(\\-?[\\d\\.e]+)' + '\\,?\\s*' +
                    '(\\-?[\\d\\.e]+)' + '\\,?\\s*' +
                    '(\\-?[\\d\\.e]+)' + '\\,?\\s*' +
                    '(\\-?[\\d\\.e]+)' + '\\,?\\s*' +
                    '(\\-?[\\d\\.e]+)' + '\\,?\\s*' +
                    '(\\-?[\\d\\.e]+)' + '\\)$'
            );                

            var matrix = rmatrix.exec( matrix );
            if (matrix) {
                matrix.shift();
            }
            return matrix || [ 1, 0, 0, 1, 0, 0 ];

        }([$(this).parents('[style*="transform"]').css('transform')]));            
    },
    drag: function( event, ui ) {

        if(!$(this).data('ui-draggable')){
            return false;
        }            

        var inst = $(this).data("ui-draggable");

        var t_pos = inst.matrix.inverse().x(new Vector(ui.position.left, ui.position.top, 0));

        ui.position.left = t_pos.e(0);
        ui.position.top = t_pos.e(1);                   

        if(inst.options.grid) {
            ui.position.left = ui.position.left - ui.position.left % inst.options.grid[0];
            ui.position.top  = ui.position.top - ui.position.top % inst.options.grid[1];                
        }

        if( inst.containment ){

            if( ui.position.left < inst.containment[0] ){
                ui.position.left = inst.containment[0];
            }

            if( ui.position.left > inst.containment[2] ){
                ui.position.left = inst.containment[2];
            }                

            if( ui.position.top < inst.containment[1] ){
                ui.position.top = inst.containment[1];
            }  

            if( ui.position.top > inst.containment[3] ){
                ui.position.top = inst.containment[3];
            }
        }
    },     
});
gal
  • 161
  • 1
  • 6
2

The top answer was working for me till i've found a glitch on draggable :(

When the containment itself is also scaled:

  • it can't be fully dragged within its containment box if scaled < 1
  • it can be dragged outside the containment box if scaled > 1

Luckily, i've found a solution here

Community
  • 1
  • 1
elegisandi
  • 454
  • 3
  • 10
1

I found I could just wrap the scaled element with another div and set draggable on it instead.

galatians
  • 738
  • 7
  • 12
1

For a long time I wonder why @GungFoo solution not working for me for Jquery Resizable but some people told it was worked. Finally I found out there are 4 points for using jQuery Draggable and Resizable with CSS Transform Scale:

  1. Using @GungFoo solutions!
  2. Using separate divs for draggable and resizable!
  3. Using proper CSS Transform Origin for divs!
  4. Using proper CSS Direction for divs!

Check some of this problems:

function resizeDiv(event, ui) {
        debugger;
        var changeWidth = ui.size.width - ui.originalSize.width; 
        var newWidth = ui.originalSize.width + changeWidth / 4; 

        var changeHeight = ui.size.height - ui.originalSize.height;
        var newHeight = ui.originalSize.height + changeHeight / 4; 

        ui.size.width = newWidth;
        ui.size.height = newHeight;

};
$('#box').resizable({
    minWidth: -($(this).width()) * 10,
    minHeight: -($(this).height()) * 10, 
    resize: resizeDiv
});

$('#box2').resizable({
    minWidth: -($(this).width()) * 10,
    minHeight: -($(this).height()) * 10, 
    resize: resizeDiv
});

$('#box3').resizable({
    minWidth: -($(this).width()) * 10,
    minHeight: -($(this).height()) * 10, 
    resize: resizeDiv
});

$('#box4').resizable({
    minWidth: -($(this).width()) * 10,
    minHeight: -($(this).height()) * 10, 
    resize: resizeDiv
});
.box{
  position:absolute;
  width:30px;
  height:30px;
  border:1px solid black;
  font-size:3pt;
  overflow:hidden;
  -webkit-transform:scale(4);
  direction:ltr;
}
#box{
  transform-origin: top left;
  top: 0;
  left:0;
}
#box2{
  transform-origin: bottom left;
  top: 250px;
  left:0;
}
#box3{
  direction:rtl;
  left: 250px;
  top: -10px;
}
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css" />
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
     
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>

<div id= 'box' class="box"> 
True styles
</div>

<div id= 'box2' class="box"> 
Bad transform-orgin
</div>

<div id= 'box3' class="box"> 
Bad direction
</div>

Thanks to @Guy ;)

n.y
  • 3,343
  • 3
  • 35
  • 54
0

Here is my solution. Some of the code posted above did not work for me.

I have a dragable image and I apply CSS transform rotate and scale onto that image. Once I scale/rotate it the position is off at drag-start. To fix it I calculate the setpoint difference from stop to start and on-drag apply a new setpoint with the difference factored in.

var $img = $("#my-image-id");

$img.posLeft  = 0;
$img.posTop  = 0;
$img.diffLeft  = 0;
$img.diffTop  = 0;

$img.draggable({
  start: function( event, ui ) {
    //check the difference from start to stop setpoints
    $img.diffLeft  = $img.posLeft-ui.position.left;
    $img.diffTop  = $img.posTop-ui.position.top;
    //console.log('start x: '+Math.floor(ui.position.left)+' y: '+Math.floor(ui.position.top));
    //console.log('diff x: '+Math.floor($img.posLeft-ui.position.left)+' y: '+Math.floor($img.posTop-ui.position.top));
  },
  drag: function( event, ui ) {
    //fix the offset bug that is applied after CSS transform, to do that just add the value difference
    ui.position.left = ui.position.left+$img.diffLeft;
    ui.position.top = ui.position.top+$img.diffTop;
    //console.log('drag x: '+ui.position.left+' y: '+ui.position.top);
  },
  stop: function( event, ui ) {
    //save the last applied setpoints
    $img.posLeft = ui.position.left;
    $img.posTop = ui.position.top;
    //console.log('stop x: '+Math.floor(ui.position.left)+' y: '+Math.floor(ui.position.top));
  }
});

The CSS transform-origin trick does fixes the bug for scale only. Rotation should be around center so you want to keep the default 50% 50%.

0

I would add this as a comment to the accepted answer from Gung Foo but I don't have the rep to comment.

I found Gung's answered worked perfectly for me however the resizable fix only worked when dragging from the bottom-right corner. I also have handles on the other three corners and found the element would shift so I had to add the fix from draggable to the resizable functions.

Maybe there's a better way, or I missed something, but I found the modified resizable below worked for me from all handles:

$(this).resizable({
    minWidth: -(contentElem.width()) * 10,  // these need to be large and negative
    minHeight: -(contentElem.height()) * 10, // so we can shrink our resizable while scaled

    // adjust the initial position to accomodate for the scale
    start: function(event, ui){
        ui.position.left = Math.round(ui.position.left/zoomScale);
        ui.position.top = Math.round(ui.position.top/zoomScale);
        ui.originalPosition.left = ui.position.left;
        ui.originalPosition.top = ui.position.top;
    },

    resize: function(event, ui) {

        var changeWidth = ui.size.width - ui.originalSize.width; // find change in width
        var newWidth = ui.originalSize.width + changeWidth / zoomScale; // adjust new width by our zoomScale

        var changeHeight = ui.size.height - ui.originalSize.height; // find change in height
        var newHeight = ui.originalSize.height + changeHeight / zoomScale; // adjust new height by our zoomScale

        ui.size.width = newWidth;
        ui.size.height = newHeight;

        // if the position is changed by a NW,NE,SW handle resize adjust for the scale
        var changeWidth = ui.size.width - ui.originalSize.width; // find change in width
        var newWidth = ui.originalSize.width + changeWidth / zoomScale; // adjust new width by our zoomScale

        var changeHeight = ui.size.height - ui.originalSize.height; // find change in height
        var newHeight = ui.originalSize.height + changeHeight / zoomScale; // adjust new height by our zoomScale

        ui.size.width = newWidth;
        ui.size.height = newHeight;

    }
});
kk64738
  • 314
  • 4
  • 15
0

I had the same problem, and the easiest way for me was NOT to use the jquery-ui's draggable functionality since it does not support CSS3 transform properties.

What worked for me really good was to implement an own draggable functionality: http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/

I know it does not solve your problem 100%, since you still want to use the jquery-ui draggable functionality, but it could help others.

eav
  • 2,123
  • 7
  • 25
  • 34
0

None of these solutions worked for me, as for one, I could not transform the parent of the draggables - only the draggables themselves were transformed.

This is how I fixed this issue:

$('.draggable').draggable(
{
    cancel: '.restrict-drag',
    scroll: false,
    containment: '#contain',

    start: function(event, ui) 
    {
        ui.position.left = 0;
        ui.position.top = 0;
    },

    drag: function(event, ui) 
    {
        ui.position.left = ui.position.left - ($(event.target).width() * Zoom);
        ui.position.top = ui.position.top - ($(event.target).height() * Zoom);
    }
});

Zoom is by default 0.

To scale the draggables I did the following:

function changeZoom(zoom)
{
    $('.draggable').each(function() { $(this).css('transform-origin', '50% 50%'); $(this).css('transform', 'scale(' + zoom + ')'); });
    Zoom = zoom; // Global Zoom-variable
}

And everything seems to work allright.

Swiffy
  • 4,401
  • 2
  • 23
  • 49
0

For different resize corner

$('.rect-item').resizable({
        handles: 'n,e,s,w,ne,se,sw,nw',
        resize: (e, ui) => {
          const zoomScale = Your scale;
          const changeWidth = ui.size.width - ui.originalSize.width; // find change in width
          const newWidth = ui.originalSize.width + changeWidth / zoomScale; // adjust new width by our zoomScale

          const changeHeight = ui.size.height - ui.originalSize.height; // find change in height
          const newHeight = ui.originalSize.height + changeHeight / zoomScale; // adjust new height by our zoomScale

          ui.size.width = newWidth;
          ui.size.height = newHeight;

          // here
          const posOrigin = ui.originalPosition;
          if (posOrigin.left !== ui.position.left) {
            ui.position.left = posOrigin.left - changeWidth / zoomScale;
          }
          if (posOrigin.top !== ui.position.top) {
            ui.position.top = posOrigin.top - changeHeight / zoomScale;
          }

        }
      });
xjinjin
  • 99
  • 1
  • 2