Dragging & Resizing CSS Transformed Elements

前端 未结 7 969
难免孤独
难免孤独 2020-12-23 16:15

If for instance, we set a -vendor-transform: rotate(40deg) css attribute on a rectangle

, all the sudden dragging and resizing becomes
相关标签:
7条回答
  • 2020-12-23 16:53

    The problem is that functions that make elements draggable, wether using jQuery UI or not, relies heavily on the native getBoundingClientRect() function to figure out the position of the element etc.

    When applying CSS3 transforms, like rotation, the values of getBoundingClientRect() or the equalent jQuery offset() function used in jQuery UI no longer works as expected, and the position of the mouse pointer gets messed up because the size of the element is suddenly wrong after it has been rotated.

    To fix it you need to add some sort of helper function that recalculates the values, and there is a monkey patch available for this that works with jQuery UI's draggable.

    It's hard to say anything about how to make the same patch work for custom code, but you'll probably have to integrate it in your custom function somehow, and it will take some coding on your part, and it's even harder to come up with something that works as a helper function out of the box for custom code one has not seen, and be aware that it is rather involved doing these calculations, see the code below :

    function monkeyPatch_mouseStart() {
         var oldFn = $.ui.draggable.prototype._mouseStart ;
         $.ui.draggable.prototype._mouseStart = function(event) {
    
                var o = this.options;
    
               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;
                    }
                  }
                }
    
                    this.helper = this._createHelper(event);
                    this._cacheHelperProportions();
    
                    if($.ui.ddmanager)
                        $.ui.ddmanager.current = this;
    
                    this._cacheMargins();
    
                    this.cssPosition = this.helper.css("position");
                    this.scrollParent = this.helper.scrollParent();
    
                this.offset = this.positionAbs = getViewOffset(this.element[0]);
                    this.offset = {
                        top: this.offset.top - this.margins.top,
                        left: this.offset.left - this.margins.left
                    };
    
                    $.extend(this.offset, {
                        click: {
                            left: event.pageX - this.offset.left,
                            top: event.pageY - this.offset.top
                        },
                        parent: this._getParentOffset(),
                        relative: this._getRelativeOffset()
                    });
    
                    this.originalPosition = this.position = this._generatePosition(event);
                    this.originalPageX = event.pageX;
                    this.originalPageY = event.pageY;
    
                    (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
    
                    if(o.containment)
                        this._setContainment();
    
                    if(this._trigger("start", event) === false) {
                        this._clear();
                        return false;
                    }
    
                    this._cacheHelperProportions();
    
                    if ($.ui.ddmanager && !o.dropBehaviour)
                        $.ui.ddmanager.prepareOffsets(this, event);
    
                    this.helper.addClass("ui-draggable-dragging");
                    this._mouseDrag(event, true);
    
                    if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
                    return true;
         };
     }
    monkeyPatch_mouseStart();
    

    And here's a FIDDLE showing it working as expected with jQuery UI's draggable and resizeable !

    0 讨论(0)
  • 2020-12-23 17:01

    This, indeed, seems to be a bug in jQuery. An easy workaround would be: surround the resizable div with a container div. Set the .draggable() to the outer div and .resizable() to the inner div. This seems to be working fine in Chromium running on Ubuntu. See Fiddle.

    I've colored the outer div to give you an idea what's happening under the hood.

    0 讨论(0)
  • 2020-12-23 17:02

    It is not bug in jQuery. Simply it is not supported. If you check jQuery UI source code you will figure out that it doesn't use transformation matrix to calculate difference between transformed object and page.

    Your example, and probably every jQ UI drag implementation suffer from this issue cause of 2 methods in JQ UI source code (around 314 line of jquery.ui.draggable.js file v1.8.23 ). Calculated offset do not matter about change in offset since rotation is done over center of element.

    You have to calculate what is that change. Here is workaround, quick and dirty. The idea is to check what is difference in bounding box of transformed element.

    Check sample here http://jsfiddle.net/mjaric/9Nqrh/

    Ignore part with first two rotations, they are just done to minimize lines of code. Third involves translation of coordinate system for calculated difference. It will offset left and top after translation is performed (note it is first in filter).

    If you want to avoid first two rotation filters, You could make code using formula for 2D rotation:

    x' = x cos f - y sin f

    y' = y cos f + x sin f

    where f is angle of rotation, but it's not that simple and also includes more lines of code where you have to calculate what is diagonal angle of original bounding box since you need initial angle of top left corner which x and y coords are comparing to x axis (positive part). Then calculate change in x-x' and y-y'. But I'm predicting some issues with sign of change and coding/debugging would take more time then I have right now. Sorry cause of that but I'm sure you can figure out what to do after reading this post.

    0 讨论(0)
  • 2020-12-23 17:02

    It looks better if we override the cursorAt:

    $("#foo").mousedown(function (e) { 
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        console.log(x);
        $("#foo").draggable("option", "cursorAt", {left: x, top:y});
    });
    

    Updated fiddle: http://jsfiddle.net/johnkoer/Ja4dY/8/

    0 讨论(0)
  • 2020-12-23 17:04

    You said you are not interested with JQuery solutions then,

    • One solution is;

      I recommend you to write your own drag and resize functions. You can handle resizing and draging on rotated objects to add their top and left with sine and cosine of that degree.

    • Another solution is;

      You can use libraries like Raphael JS to create objects to transform, drag and resize. Raphael JS uses svg!

      For more information about Raphael JS

    • Yet another solution is;

      If you do not want to use library like Raphael JS, you can directly use SVG with JQuery

      For more information about SVG

    Cannot write more details now, I expand this solutions tomorrow.

    Hope these help for now.

    0 讨论(0)
  • 2020-12-23 17:07

    I found this... It's a working example plus info, demo and download link.

    jquery-ui-rotation-using-css-transform -> live-demo

    He use his own libraries, but if you are interest in the subject, you can read and learn how he get it.

    cheers and good luck.

    Gmo.-

    Btw, the web is in Russian, but with google translate you can manage ;-)

    0 讨论(0)
提交回复
热议问题