Jquery draggable with zoom problem

2020-02-03 07:01发布

问题:

I am working on a page in witch all its contents are scaled by using zoom. The problem is that when I drag something in the page the dragging item gets a bad position that seems relative to the zoom amount.

To solve this I tried to do some math on the position of the draggable component, but seems that even tho visually its corrected, the "true" position its not recalculated.

here is some code to explain better:

var zoom = Math.round((parseFloat($("body").css("zoom")) / 100)*10)/10;

var x = $(this).data('draggable').position;
$(this).data('draggable').position.left = Math.round(x.left/zoom);
$(this).data('draggable').position.top = Math.round(x.top/zoom);

Any help would be greatly appreciated

回答1:

Took a lot of time and effort to fix this, but finally, I have a solution that works.

This solution works for both Firefox and IE. #canvas is the div that contains the draggable. Note that we have to make sure the elements stays inside the canvas manually.

This also works if if canvas has a different zoom level than the rest of the page.

var pointerX;
var pointerY;
$(c).draggable({
  start : function(evt, ui) {
    pointerY = (evt.pageY - $('#canvas').offset().top) / zoom - parseInt($(evt.target).css('top'));
    pointerX = (evt.pageX - $('#canvas').offset().left) / zoom - parseInt($(evt.target).css('left'));
  },
  drag : function(evt, ui) {
    var canvasTop = $('#canvas').offset().top;
    var canvasLeft = $('#canvas').offset().left;
    var canvasHeight = $('#canvas').height();
    var canvasWidth = $('#canvas').width();

    // Fix for zoom
    ui.position.top = Math.round((evt.pageY - canvasTop) / zoom - pointerY); 
    ui.position.left = Math.round((evt.pageX - canvasLeft) / zoom - pointerX); 

    // Check if element is outside canvas
    if (ui.position.left < 0) ui.position.left = 0;
    if (ui.position.left + $(this).width() > canvasWidth) ui.position.left = canvasWidth - $(this).width();  
    if (ui.position.top < 0) ui.position.top = 0;
    if (ui.position.top + $(this).height() > canvasHeight) ui.position.top = canvasHeight - $(this).height();  

    // Finally, make sure offset aligns with position
    ui.offset.top = Math.round(ui.position.top + canvasTop);
    ui.offset.left = Math.round(ui.position.left + canvasLeft);
  }
});


回答2:

var zoom = $('#canvas').css('zoom');    
$('#dragme').draggable({
        drag: function(evt,ui)
        {
             var factor = (1 / zoom) -1);

             ui.position.top += Math.round((ui.position.top - ui.originalPosition.top) * factor);
             ui.position.left += Math.round((ui.position.left- ui.originalPosition.left) * factor);    
        }                 
    });


回答3:

If CSS Zoom is performed specifically on the html body, there is a very easy fix, but it does require you to modify the source. That's a no-go for some, but I'll post the solution here anyway.

So the draggable functionality of jQuery UI figures out the mouseposition using a method called _generatePosition. The problem is that it doesn't take zoom into account (duh), so we merely have to divide its outcome by the zoom level and everything will work.

So turn this:

    return {
        top: (

            // The absolute mouse position
            pageY -

            // Click offset (relative to the element)
            this.offset.click.top -

            // Only for relative positioned nodes: Relative offset from element to offset parent
            this.offset.relative.top -

            // The offsetParent's offset without borders (offset + border)
            this.offset.parent.top +
            ( this.cssPosition === "fixed" ?
                -this.offset.scroll.top :
                ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
        )/ ,
        left: (

            // The absolute mouse position
            pageX -

            // Click offset (relative to the element)
            this.offset.click.left -

            // Only for relative positioned nodes: Relative offset from element to offset parent
            this.offset.relative.left -

            // The offsetParent's offset without borders (offset + border)
            this.offset.parent.left +
            ( this.cssPosition === "fixed" ?
                -this.offset.scroll.left :
                ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
        )
    };

in

    var zoomLevel = parseFloat($('body').css("zoom") || "1.0");

    return {
        top: (

            // The absolute mouse position
            pageY -

            // Click offset (relative to the element)
            this.offset.click.top -

            // Only for relative positioned nodes: Relative offset from element to offset parent
            this.offset.relative.top -

            // The offsetParent's offset without borders (offset + border)
            this.offset.parent.top +
            ( this.cssPosition === "fixed" ?
                -this.offset.scroll.top :
                ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
        ) / zoomLevel,
        left: (

            // The absolute mouse position
            pageX -

            // Click offset (relative to the element)
            this.offset.click.left -

            // Only for relative positioned nodes: Relative offset from element to offset parent
            this.offset.relative.left -

            // The offsetParent's offset without borders (offset + border)
            this.offset.parent.left +
            ( this.cssPosition === "fixed" ?
                -this.offset.scroll.left :
                ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
        ) / zoomLevel
    };

That's all. I suggest saving it under a specific name so it's easily recognized that it has been modified. E.g.: jquery-ui.ZOOMFIX.js

I used the jquery-ui.js file from the following package: https://jqueryui.com/resources/download/jquery-ui-1.12.1.zip



回答4:

Are you taking the scroll position, margin and padding into account? Such as:

x.left + 
parseInt($(this).css('margin-left')) + 
parseInt($(this).css('padding-left')) + 
parseInt($(this).parent().scrollLeft());

x.top + 
parseInt($(this).css('margin-top')) + 
parseInt($(this).css('padding-top')) + 
parseInt($(this).parent().scrollTop());

Then adjusting for the zoom value as needed?



回答5:

I was struggling with this for some hours. I had a grid where I started of with scaling it by setting the font-size (125% etc on the container). This worked well until I came into images that didn't scale with the grid.

Thought I'd use zoom instead and it worked brilliantly. Then I relized by draggable/droppable wasn't scaling with it hmm.. Spend several hours trying to destroy and re-init the jqueryUI objects but nothing worked.

Then finally I realized I could use a combination of font-size for zooming the grid and css-zoom for the images. Now everything works and I can drag/drop with the same instance of jQueryUI.

Hope this approach helps someone else. :-)