jQuery Drag/Resize with CSS Transform Scale

前端 未结 13 1885
忘了有多久
忘了有多久 2020-11-27 13:39

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

相关标签:
13条回答
  • 2020-11-27 14:24

    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

    0 讨论(0)
  • 2020-11-27 14:28

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

    0 讨论(0)
  • 2020-11-27 14:29

    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.

    0 讨论(0)
  • 2020-11-27 14:30

    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;
    
        }
    });
    
    0 讨论(0)
  • 2020-11-27 14:32

    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];
                }
            }
        },     
    });
    
    0 讨论(0)
  • 2020-11-27 14:34

    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

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