Enhancing Drag and Drop Functionality with jQuery and CSS Zoom Effect

I am facing an issue in my application with a div that has variable CSS zoom.

Due to this, the coordinate space gets disrupted. The page pixel is no longer equal to 1px when hovering over the zoomable area :)

This breakdown in the coordinate system is causing problems with intersection detection for droppables.

I am seeking guidance on where to begin addressing this issue.

Here is a demo I have created:

Fiddle:

http://jsfiddle.net/crRvp/1/

HTML:

<div id="dse">
    <div id="ds">Drop here!</div>
</div>
<p>
<div id="widget">Drag me!</div>
</p>

CSS:

#dse{
    position:relative;
    width:640px;
    height:360px;
    zoom:0.75;
    -moz-transform: scale(0.75);
    -moz-transform-origin:0 0;
    -o-transform: scale(0.75);
    -o-transform-origin:0 0;
}

#ds{
    position:relative;
    zoom:1;
    width:640px;
    height:360px;
    border: solid 1px black;
    font-size:2em;
}

#widget{
    position:relative;
    width:150px;
    height:50px;
    border: solid 1px black;
}

.droppableActive{
    background-color:red;
}

.droppableHover{
    background-color:green;
}

JS:

$("#widget").draggable({
    opacity     : 0.7,
    scroll      : false,
    revert: function(dropped){
          var wasDropped = dropped && dropped[0].id == "ds";
          if(!wasDropped){$(this).animate({ top: 0, left: 0 }, 'slow');}
          return !wasDropped;
    }
    ,
    stop: function(evt, ui) {

        var canvasLeft = $('#ds').offset().left;
        var canvasTop = $('#ds').offset().top;

        alert('ui.position.left: ' + (ui.position.left) + ' canvasLeft: ' + canvasLeft);
        alert('ui.position.top: ' + (ui.position.top) + ' canvasTop: ' + canvasTop);
    }
});

$("#ds").droppable({
    activeClass: 'droppableActive',
    hoverClass: 'droppableHover',
    accept: "#widget",
    drop: function(evt, ui){
        id = ui.draggable.attr('id');
        alert('id: ' + id);
    }
});

Answer №1

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Javascript Object and Conditional Statement

I am working on a Dygraph object creation script that includes a data variable. However, I want to avoid passing in this variable for older browsers. Here is the code snippet: function prime() { g = new Dygraph( document.getElementById("g"), d ...

Animate jQuery Images - Transform and smoothly reveal element

Hey there! I've managed to create a color switcher that gives a sneak peek of different themes. Currently, it simply switches the image source and loads the new image. But I'm curious if it's possible to add a fadeIn effect to enhance the t ...

Tips on capturing a URL using JQuery

Currently, I am in the process of importing an external HTML file into my webpage. Within this file, there is a JavaScript method linked to a form submission event. function ValidateInput(){ //some code if(validationFailed){ ...

Transforming a jQuery menu into an active selection with Vue JS

I am looking to transition away from using jQuery and instead utilize Vue for the front end of a menu. Specifically, I need to add an active class and a 'menu-open' state to the appropriate nested list items, similar to what is achieved in the pr ...

The bootstrap table did not meet my expectations as I had hoped

I am currently using Bootstrap to create a basic two-column table similar to the one on the Bootstrap website here: http://getbootstrap.com/css/#tables. To achieve this, I have implemented a javascript function to display the table as shown below: $(&bso ...

Overflow container list items in Bootstrap

How can I prevent the text in each item of this dropdown list from wrapping and display them in a single line? .autocomplete-items { /* position: absolute; */ /* border: 1px solid #ccc; */ /* border-top: none; */ /* border-radius: 0 0 ...

Ways to determine if new data has been added to a MySQL table

Can anyone explain how the Facebook notification system operates? Here is my code snippet: (function retrieve_req() { $.ajax({ url: 'request_viewer_and_its_utilities.php', success: function(data) { $('.inte ...

Align text in the middle using CSS

I want to apply the following css styles #header { color: #333; width: 900px; float: left; padding: 10px; border: 1px solid #ccc; height: 150px; margin: 10px 0px 5px 0px; background-image: linear-gradient(to top, #CACACA 0%, #EFEFEF 100%); } With ...

Showing K2 Item Content instead of Title in JoomlaLearn how to showcase the content of a K2

I used this code to display the title of a k2 item. However, I now want to display the Content of the k2 item instead. What should I replace in my code for this to work? Here's my current code: <script> jQuery (document).ready(function() { ...

The conceal feature doesn't appear to be functioning properly on jQuery Mobile

I am facing an issue with a small mobile app built using jQuery Mobile. Within a div, I have three buttons and other content. My goal is to hide these three buttons if the user's device is running on Windows (WP). The buttons are defined as follows: ...

I'm having issues with my navigation bar, and the border on the right side is not functioning correctly

I've been struggling to get the right border positioned between each section of my nav bar. I've tried wrapping each word in a separate span, but it doesn't seem to cooperate. The borders end up on the side and are too small to fill the enti ...

Ensure that the cursor is consistently positioned at the end within a contenteditable div

I'm working on a project where I need to always set the caret position at the end of the text. By default, this works fine but when dynamically adding text, the caret position changes to the starting point in Chrome and Firefox (Internet Explorer is s ...

Is it practical to extract the page type/name from the CSS of the selected tab?

My website has multiple tabs on a single page and I want to integrate Google Analytics code. However, since all the tabs are part of the same page, I need a way to track which tab the user is interacting with. Would using CSS to check for the selected tab ...

css table cells with overflow

I need help with a table where I have text in one of the td cells that goes beyond the right border and wraps to the next line. What I want is for the overflowing text to be hidden when it reaches the border, showing only the last 3 visible characters repl ...

Center the content of the HTML body

Despite my best efforts, I seem to be making a mistake somewhere. While creating a bootstrap website at , the site appears well and aligned in the center. However, when I try to adjust the screen size to less than 850px or even smaller, all the content shi ...

A platform for creating ER and flow diagrams specifically tailored for web applications, utilizing open source software

Our team is currently working on creating a web application that enables users to create diagrams, such as flow or ER diagrams. We are looking for ways to convert these diagrams into XML or other formats for representation. Are there any open-source soft ...

Retrieve data from jQuery and send it to PHP multiple times

<input type="checkbox" value="<?= $servicii_content[$j]['title'] ?>" name="check_list" id="check" /> By using jQuery, I am able to extract multiple values from the table above once the checkboxes are checked. Here's how: var te ...

The Intriguing Riddle of HTML Semantic

I've been working on a puzzle presented in the link provided below: HTML Structure Challenge This mind-teaser consists of three questions: Modify the HTML code of the website to enhance its structure as follows: Replace the generic outer div eleme ...

Change the background of the play/stop icon with a jquery toggle

There is a list of video links with play icons as backgrounds in front of them. When a user clicks on a link, the video will start playing in a player located to the left of the links. The clicked link's background icon changes to a 'stop' i ...

Can you provide tips on leveraging CSS as a prop for a Vuetify v-tab component?

Currently, I am in the process of updating my website to simplify color palette swapping. We are still experimenting with different colors that work well together. Our stack includes Vue and Vuetify, with SCSS generating our primary CSS file. Most of our c ...