In my situation, I encountered a similar issue where both droppable and draggable items were within a container that had the zoom
CSS property applied.
The problem with the draggable item can be resolved by implementing the 'dragFix' fix:
var zoomScale = parseFloat($('.container').css('zoom'));
$('.drag__item').draggable({
drag: function (event, ui) {
var changeLeft = ui.position.left - ui.originalPosition.left;
var newLeft = ui.originalPosition.left + changeLeft / zoomScale;
var changeTop = ui.position.top - ui.originalPosition.top;
var newTop = ui.originalPosition.top + changeTop / zoomScale;
ui.position.left = newLeft;
ui.position.top = newTop;
}
});
To address the issue with the droppable item, it is necessary to modify the jQuery-UI code. Since editing the library source code directly is not recommended, we can override the intersect
method.
The problem arises from the x1
and y1
coordinates (top, left of draggable item) getting distorted after applying the previous 'dragFix' fix, causing the intersection detection to malfunction. To rectify this, we need to normalize the x1
and y1
coordinates.
var intersect = $.ui.intersect = (function () {
function isOverAxis(x, reference, size) {
return (x >= reference) && (x < (reference + size));
}
return function (draggable, droppable, toleranceMode, event) {
if (!droppable.offset) {
return false;
}
var x1 = draggable.offset.left + draggable.position.left - draggable.originalPosition.left + draggable.margins.left,//here is the fix for scaled container
y1 = draggable.offset.top + draggable.position.top - draggable.originalPosition.top + draggable.margins.top,//here is the fix for scaled container
x2 = x1 + draggable.helperProportions.width,
y2 = y1 + draggable.helperProportions.height,
l = droppable.offset.left,
t = droppable.offset.top,
r = l + droppable.proportions().width,
b = t + droppable.proportions().height;
switch (toleranceMode) {
case 'fit':
return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
case 'intersect':
return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
x2 - (draggable.helperProportions.width / 2) < r && // Left Half
t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
y2 - (draggable.helperProportions.height / 2) < b); // Top Half
case 'pointer':
return isOverAxis(event.pageY, t, droppable.proportions().height) &&
isOverAxis(event.pageX, l, droppable.proportions().width);
case 'touch':
return (
(y1 >= t && y1 <= b) || // Top edge touching
(y2 >= t && y2 <= b) || // Bottom edge touching
(y1 < t && y2 > b) // Surrounded vertically
) && (
(x1 >= l && x1 <= r) || // Left edge touching
(x2 >= l && x2 <= r) || // Right edge touching
(x1 < l && x2 > r) // Surrounded horizontally
);
default:
return false;
}
};
})();
Starting from jQuery 1.12, the $.ui.intersect function is scoped in such a way that direct modification is not possible afterward. It is invoked in $.ui.ddmanager as a local variable, so even if you modify $.ui.intersect, it will not take effect. Customizing it requires a more intricate approach. You can achieve it by rescoping intersect and then redefining the drag and drop method on $.ui.ddmanager to utilize the modified intersect function.
This answer provides more insights
$.ui.ddmanager.drag = function (draggable, event) {
// If your page is highly dynamic, consider using this option. It recalculates positions
// every time the mouse moves.
if (draggable.options.refreshPositions) {
$.ui.ddmanager.prepareOffsets(draggable, event);
}
// Iterate through all droppables and assess their positions based on specific tolerance options
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function () {
if (this.options.disabled || this.greedyChild || !this.visible) {
return;
}
var parentInstance, scope, parent,
intersects = intersect(draggable, this, this.options.tolerance, event),
c = !intersects && this.isover ?
'isout' :
(intersects && !this.isover ? 'isover' : null);
if (!c) {
return;
}
if (this.options.greedy) {
// Find droppable parents with the same scope
scope = this.options.scope;
parent = this.element.parents(':data(ui-droppable)').filter(function () {
return $(this).droppable('instance').options.scope === scope;
});
if (parent.length) {
parentInstance = $(parent[0]).droppable('instance');
parentInstance.greedyChild = (c === 'isover');
}
}
// Moving into a greedy child
if (parentInstance && c === 'isover') {
parentInstance.isover = false;
parentInstance.isout = true;
parentInstance._out.call(parentInstance, event);
}
this[c] = true;
this[c === 'isout' ? 'isover' : 'isout'] = false;
this[c === 'isover' ? '_over' : '_out'].call(this, event);
// Leaving a greedy child
if (parentInstance && c === 'isout') {
parentInstance.isout = false;
parentInstance.isover = true;
parentInstance._over.call(parentInstance, event);
}
});
}
$.ui.ddmanager.drop = function (draggable, event) {
var dropped = false;
// Create a copy of the droppables in case the list changes during the drop (#9116)
$.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function () {
if (!this.options) {
return;
}
if (!this.options.disabled && this.visible &&
intersect(draggable, this, this.options.tolerance, event)) {
dropped = this._drop.call(this, event) || dropped;
}
if (!this.options.disabled && this.visible && this.accept.call(this.element[0],
(draggable.currentItem || draggable.element))) {
this.isout = true;
this.isover = false;
this._deactivate.call(this, event);
}
});
return dropped;
}
Here is an example on JSFiddle