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
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);
}
});
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:
Fix:
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 + ')'
});
}
});
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;
},
});
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).
A much simpler solution is to wrap the scaled content with another div and set that to be draggable instead.
//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'));
}
});
Here is a working example showing raghugolconda's method.
I utilized the data
method, instead of setting non-standard attr
values.
I wrapped it all into a jQuery plugin called $.fn.draggablePatched
.
function main() {
$('#draggable').draggablePatched({
cursor: 'pointer'
});
}
/* jquery.draggable-patched.js */
(function($) {
var __dx, __dy;
var __recoupLeft, __recoupTop;
var parseIntSafe = function(value) {
return (function(n) {
return isNaN(n) ? 0 : n;
})(parseInt(value, 10));
}
$.fn.draggablePatched = function(options) {
options = options || {};
return this.draggable({
cursor: options.cursor || 'move',
zIndex: 100,
drag: function(event, ui) {
__dx = ui.position.left - ui.originalPosition.left;
__dy = ui.position.top - ui.originalPosition.top;
ui.position.left = ui.originalPosition.left + __dx + __recoupLeft;
ui.position.top = ui.originalPosition.top + __dy + __recoupTop;
if (options.drag) {
options.drag(event, ui);
}
},
start: function(event, ui) {
var left = parseIntSafe($(this).css('left'));
var top = parseIntSafe($(this).css('top'));
__recoupLeft = left - ui.position.left;
__recoupTop = top - ui.position.top;
if (options.start) {
options.start(event, ui);
}
},
stop: function(event, ui) {
$(this).animate({
left: $(this).data('oriLeft'),
top: $(this).data('oriTop')
}, 1000);
if (options.stop) {
options.stop(event, ui);
}
},
create: function(event, ui) {
$(this).data({
oriLeft: $(this).css('left'),
oriTop: $(this).css('top')
});
if (options.create) {
options.create(event, ui);
}
}
});
}
})(jQuery);
main();
body {
background-color: blue;
}
#draggable {
position: absolute;
left: 50px;
top: 50px;
width: 350px;
height: 350px;
background-color: rgba(0, 0, 0, 0.5);
border: 1px solid black;
color: white;
font-size: 4em;
line-height: 350px;
text-align: center;
-moz-transform: rotate(-45deg) scale(0.5);
-webkit-transform: rotate(-45deg) scale(0.5);
transform: rotate(-45deg) scale(0.5);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div id="draggable">
Hello!
</div>