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.
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
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); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With