jQuery UI Draggable + CSS Transform Causes “Jumpin

2020-06-12 04:19发布

问题:

EDIT: I solved it. But StackOverflow isn't letting me mark my answer as the solution, so I simply am not going to.

I'm having an issue with regard to using Draggable with a CSS transformed parent. Basically, I need to use absolute positioning to spawn a Draggable div directly underneath the cursor. When absolute positioning is used with CSS transforms, the draggable element does a bit of jumping right as the dragging takes place. After the jump takes place, behavior proceeds as expected. The jump does not occur if no transformations are applied to the draggable or the parent div.

Here's a fiddle that shows exactly what the problem is: http://jsfiddle.net/qBubN/7/

body {
     background-color: blue;   
}

#draggable {
    position: absolute;
    left: 50px;
    top: 50px;
    background-color: rgba(0,0,0,0.5);
    border: 1px solid black;
    width: 350px;
    height: 350px;
    color: white;
    -moz-transform: scale(0.5);
    -webkit-transform: scale(0.5);
    transform: scale(0.5);}

     $("#draggable").draggable({
            scroll: true, 
            distance: 5,
            grid : [ 10, 10 ],
            start: function (event, ui) {
            }
        });

<html>
    <body>
       <div id="draggable">
           Hello!
        </div>
    </body>
</html>

Already tried applying this patch, to no avail. There's a (good) chance this fix is too old to work. There's also a chance I am applying the patch wrongly. Webkit and jQuery draggable jumping

回答1:

//css3 transform bug with jquery ui drag - fixed(works fine whether position, absolute or relative)
var __dx;
var __dy;
var __scale=0.5;
var __recoupLeft, __recoupTop;

$("#draggable").draggable({
    //revert: true,
    zIndex: 100,
    drag: function (event, ui) {
        //resize bug fix ui drag `enter code here`
        __dx = ui.position.left - ui.originalPosition.left;
        __dy = ui.position.top - ui.originalPosition.top;
        //ui.position.left = ui.originalPosition.left + ( __dx/__scale);
        //ui.position.top = ui.originalPosition.top + ( __dy/__scale );
        ui.position.left = ui.originalPosition.left + (__dx);
        ui.position.top = ui.originalPosition.top + (__dy);
        //
        ui.position.left += __recoupLeft;
        ui.position.top += __recoupTop;
    },
    start: function (event, ui) {
        $(this).css('cursor', 'pointer');
        //resize bug fix ui drag
        var left = parseInt($(this).css('left'), 10);
        left = isNaN(left) ? 0 : left;
        var top = parseInt($(this).css('top'), 10);
        top = isNaN(top) ? 0 : top;
        __recoupLeft = left - ui.position.left;
        __recoupTop = top - ui.position.top;
    },
    stop: function (event, ui) {
        $(this).css('cursor', 'default');
        //alternate to revert (don't use revert)
        $(this).animate({
            left: $(this).attr('oriLeft'),
            top: $(this).attr('oriTop')
        }, 1000)
    },
    create: function (event, ui) {
        $(this).attr('oriLeft', $(this).css('left'));
        $(this).attr('oriTop', $(this).css('top'));
    }
});


回答2:

I found the solution.

The solution is the completely avoid position:absolute; when using Draggable and CSS transforms. You can easily get manipulate anything from absolute/window/whatever coordinates into relative, so that's just what I did.

In my case, I was spawning a Draggable element underneath the mouse. I calculated the relative position based on the mouse position with the offset() of the element (both in window coordinates) and then divided by the scale of the parent div.

Here's a snippet:

// ops.[x|y] is the mouse position in window coords
// parentDiv.offset().[left|right] is the div position in window coords

// get the scale transform matrix from our poorly written panzooming lib
var mtx = graph.parentDiv.panzoom('getMatrix');
var zx = mtx[0];
var zy = mtx[3];

// calculate the relative position
var x = (ops.x - parentDiv.offset().left) / zx;
var y = (ops.y - parentDiv.offset().top) / zy;

// set some initial css
parentDiv.css('position', 'relative')
         .css('left', x + 'px')
         .css('top', y + 'px');

// initialize the draggable
parentDiv.draggable({
    stack: $(graph.parentDiv).children(),
    drag: function(e, ui){
        var mtx = graph.parentDiv.panzoom('getMatrix');
        var zoomScaleX = mtx[0];
        var zoomScaleY = mtx[3];

        // scale the delta by the zoom factor
        var dx = ui.position.left - ui.originalPosition.left;
        var dy = ui.position.top - ui.originalPosition.top;

        ui.position.left = ui.originalPosition.left + (dx / zoomScaleX);
        ui.position.top = ui.originalPosition.top + (dy / zoomScaleY);
    }
});


回答3:

A much simpler solution is to wrap the scaled content with another div and set that to be draggable instead.



回答4:

position:absolute; is indeed problematic. I however found an alternative solution that prevents the jump, while keeping an absolute basis for coordinates and maintain position, by flipping the css position to relative on mousedown, and restore it to absolute on mouseup, such as the following:

$('#container').on('mousedown', 'canvas', function (e) {
    e.currentTarget.style.position = 'relative';
}).on('mouseup', 'canvas', function (e) {
    if (e.currentTarget.style.position !== 'absolute'){
        e.currentTarget.style.position = 'absolute';
    }
});

It works well for mouse events. And to resolve the issue for touch events, along with the 'touchpunch' plugin, I additionally also had to cancel 'click' events (for mobile and touch enabled modality only).



回答5:

Expanding on "raghugolconda" top answer:

I've had dragging speed and jumping problems with jQueryUI draggable and CSS transform: scale()

The image container is scalable with zoom slider, the red square is draggable.

What happened when i tried to drag the red element:

  • Element jumped when you tried to drag it
  • Drag speed was too low when Zoom was over 100%
  • Drag speed was too fast when Zoom was under 100%

Fix:

  1. Calculate fraction (scale value) from jQuery slider. Here is my slider than transforms image container:

    var fraction = 1;  
    
    $("#slider").slider({
        value: 0,
        min: -70,
        max: 70,
        step: 10,
        slide: function (event, ui) {
            fraction = (1 + ui.value / 100);  
    
            $("#infoSlider").text('Zoom: ' + Math.floor(fraction * 100) + '%');
    
            $('.image_scalable_container').css({
                '-webkit-transform': 'scale(' + fraction + ')',
                '-moz-transform': 'scale(' + fraction + ')',
                '-ms-transform': 'scale(' + fraction + ')',
                '-o-transform': 'scale(' + fraction + ')',
                'transform': 'scale(' + fraction + ')'
            });
    
        }
    });
    
  2. Overwrite jQuery UI draggable drag and start functions.

In drag you modify the dragging speed (scale 0.9 means drag_speed = 1 / 0.9 = 1.11 )

Here is my example:

$("#marker").draggable({
    //revert: true,
    zIndex: 100,
    drag: function (event, ui) {
        var drag_speed = 1 / fraction;

        __dx = (ui.position.left - ui.originalPosition.left) * drag_speed;
        __dy = (ui.position.top - ui.originalPosition.top) * drag_speed;
        ui.position.left = ui.originalPosition.left + (__dx);
        ui.position.top = ui.originalPosition.top + (__dy);            
        ui.position.left += __recoupLeft;
        ui.position.top += __recoupTop;
    },        
    start: function (event, ui) {            
        //resize bug fix ui drag
        var left = parseInt($(this).css('left'), 10);
        left = isNaN(left) ? 0 : left;
        var top = parseInt($(this).css('top'), 10);
        top = isNaN(top) ? 0 : top;
        __recoupLeft = left - ui.position.left;
        __recoupTop = top - ui.position.top;
    },
});