jQuery UI - Draggable issue with Zoom

Viewed 45

I'm facing an issue while dragging the elements inside canvas, here my canvas is div. Inside a div I have two draggable elements. I just applied a zoom property for those draggable elements. I would have applied the zoom for a parent div but based on the requirement I should apply zoom only for the draggable element. However after applying the zoom for the draggable elements it's not responding well, when I try to move it to the edge(both X & Y) of the canvas. I tried some calculation but it's working.

var zoom = $('#canvas').css('zoom');
var canvasHeight = $('#canvas').height();
var canvasWidth = $('#canvas').width();

$('#dragme').draggable({
    drag: function(evt,ui)
  {
        // zoom fix
    //    ui.position.top = Math.round(ui.position.top / zoom);
    //    ui.position.left = Math.round(ui.position.left / zoom);
        
        // don't let draggable to get outside of the 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();  
    }                 
});
$('#dragme1').draggable({
    drag: function(evt,ui)
  {  
        // zoom fix
   //  ui.position.top = Math.round(ui.position.top / zoom);
     //ui.position.left = Math.round(ui.position.left / zoom);
        
        // don't let draggable to get outside of the 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();  
    }                 
});
#canvas {
    width:500px;
    height:250px;
    border:1px solid #444;
    
}
#dragme {
    width:100px;
    height:50px;
    background:#f30;
    zoom:0.8;
}

#dragme1 {
    width:100px;
    height:50px;
    background:blue;
    zoom:0.8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.js"></script>
<div id="canvas">
    <div id="dragme"></div>
     <div id="dragme1"></div>
</div>

1 Answers

There were a number of problems.

  1. This part is problematic: var zoom = $('#canvas').css('zoom'); because you attempted to use the canvas but your "zoom" css property is not for your #canvas.

  2. Since the zoom property is not set in the inline css, you cannot get it from .css function. You need to use a computed style like var zoom = getComputedStyle(this).zoom;

  3. The zoom interferes with finding the appropriate coordinates: for example, if the mouse moves by 10px, due to the zoom, it will only seem to move for 8px. It's very tricky to correct this issue (but I managed to do it nevertheless, by recording a value at the start: and performing some other mathematical operations. See below).

  4. The restriction to the canvas: while the checks ui.position.left < 0 and ui.position.top < 0 continue to work, the other two don't work due to zooming. The other two should be (ui.position.left + $(this).width()) * zoom > canvasWidth and (ui.position.top + $(this).height()) * zoom > canvasHeight. Then again some more math operations to figure out the correct ui.position.left and ui.position.top

  5. Even if the zoom was 1, your original code would not work properly for the blue object #dragme1: when the position.top equals 0 it is still not at the upper corner of the canvas, and when it is at the maximum value, it's already outside the canvas. This is due to due to having position:relative, which means its positioning is affected by the height of the red object. One way to solve it is by having them positioned absolutely, with the canvas positioned relative. And to get the appropriate starting position, the blue object starts with top: 50px, where 50px is the height of the red object.

  6. After 1-5, I found that scrollY has some effects here, so I modified the startY calculations.

But then, there still seems to be an issue about "sudden jump" when the zoom is very different, say, 0.2. Some time later I may (or may not) edit it to deal with those cases. But I think it is ok for a zoom level of 0.8. And in any case this post has demonstrated how to get the basic features right.

It may possibly be cleaner to implement the dragging oneself using event listeners (if you know what you're doing) than being tied to jquery-ui (especially due to the issue #3 above).

var canvasHeight = $('#canvas').height();
var canvasWidth = $('#canvas').width();

$('#dragme').draggable({
    start: function (evt, ui) {
        var zoom = getComputedStyle(this).zoom;
        $(this).data('startX', ui.position.left)
               .data('startY', ui.position.top - window.scrollY)
    },
    drag: function (evt, ui) {
        let diffX = ui.position.left - $(this).data('startX')
        let diffY = ui.position.top - $(this).data('startY')

        var zoom = getComputedStyle(this).zoom;

        ui.position.left = +$(this).data('startX') + diffX / zoom
        ui.position.top = +$(this).data('startY') + diffY / zoom

        if (ui.position.left < 0)
            ui.position.left = 0;
        if ((ui.position.left + $(this).width()) * zoom > canvasWidth)
            ui.position.left = canvasWidth / zoom - $(this).width();
        if (ui.position.top < 0)
            ui.position.top = 0;
        if ((ui.position.top + $(this).height()) * zoom > canvasHeight)
            ui.position.top = canvasHeight / zoom - $(this).height();
    }
});

$('#dragme1').draggable({
    start: function (evt, ui) {
        var zoom = getComputedStyle(this).zoom;
        $(this).data('startX', ui.position.left)
               .data('startY', ui.position.top - window.scrollY)
    },
    drag: function (evt, ui) {
        let diffX = ui.position.left - $(this).data('startX')
        let diffY = ui.position.top - $(this).data('startY')

        var zoom = getComputedStyle(this).zoom;

        ui.position.left = +$(this).data('startX') + diffX / zoom
        ui.position.top = +$(this).data('startY') + diffY / zoom

        if (ui.position.left < 0)
            ui.position.left = 0;
        if ((ui.position.left + $(this).width()) * zoom > canvasWidth)
            ui.position.left = canvasWidth / zoom - $(this).width();
        if (ui.position.top < 0)
            ui.position.top = 0;
        if ((ui.position.top + $(this).height()) * zoom > canvasHeight)
            ui.position.top = canvasHeight / zoom - $(this).height();
    }
});
#canvas {
    width: 500px;
    height: 250px;
    border: 1px solid #444;
    position: relative;
}

#dragme {
    width: 100px;
    height: 50px;
    background: #f30;
    zoom: 0.8;
    position: absolute;
}

#dragme1 {
    width: 100px;
    height: 50px;
    background: blue;
    zoom: 0.8;
    position: absolute;
    top: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.js"></script>
<div id="canvas">
    <div id="dragme"></div>
     <div id="dragme1"></div>
</div>

Related