Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dragging elements on a scaled div

I've tried using jquery's built in draggable and I've tried using custom drag functions with no avail. Both have their respected issues and I will try to highlight both of them.

Basically, I am trying to allow the dragging of an element that is on a scaled div container. The following methods work okay on a scaled element that is less than around 2. But if you go any higher than that, we see some issues.

Any help would be appreciated. Thank you for your time.

HTML

<div id="container">
    <div id="dragme">Hi</div>
</div>

Method 1 (Jquery draggable function)

I've tried the jquery draggable function as you can see in this jsfiddle example.

The problems I found in this example are the following:

  • Biggest concern: The droppable container does not change when it is scaled up. So if the element is being dragged over part of the scaled container that isn't a part of it's original size, it will fail.
  • When you click to drag a div, it teleports a little bit away from the mouse and is not a seamless drag.

JS

var percent = 2.5;

$("#dragme").draggable({
    zIndex: 3000,
    appendTo: 'body',
    helper: function (e, ui) {
        var draggable_element = $(this),
            width = draggable_element.css('width'),
            height = draggable_element.css('height'),
            text = draggable_element.text(),
            fontsize = draggable_element.css('font-size'),
            textalign = draggable_element.css('font-size');
        return $('<div id="' + draggable_element.id + '" name="' + draggable_element.attr('name') + '" class="text">' + text + '</div>').css({
            'position': 'absolute',
                'text-align': textalign,
                'background-color': "red",
                'font-size': fontsize,
                'line-height': height,
                'width': width,
                'height': height,
                'transform': 'scale(' + percent + ')',
                '-moz-transform': 'scale(' + percent + ')',
                '-webkit-transform': 'scale(' + percent + ')',
                '-ms-transform': 'scale(' + percent + ')'
        });
    },
    start: function (e, ui) {
        $(this).hide();
    },
    stop: function (e, ui) {
        $(this).show();
    }
});

$("#container").droppable({
    drop: function (event, ui) {
        var formBg = $(this),
            x = ui.offset.left,
            y = ui.offset.top,
            drag_type = ui.draggable.attr('id');

        var element_top = (y - formBg.offset().top - $(ui.draggable).height() * (percent - 1) / 2) / percent,
            element_left = (x - formBg.offset().left - $(ui.draggable).width() * (percent - 1) / 2) / percent;

        $(ui.draggable).css({
            'top': element_top,
                'left': element_left
        });

    }
});

Method 2 - Custom drag function

I've tried using a custom drag function but it unusable after around a 2 scale.

  • jsfiddle on a scale(2) - Looks like the draggable div is having a seizure.
  • jsfiddle on a scale(2.5) - The draggable div flys away when you try to drag it.

JS

(function ($) {
    $.fn.drags = function (opt) {

        opt = $.extend({
            handle: "",
            cursor: "move"
        }, opt);

        if (opt.handle === "") {
            var $el = this;
        } else {
            var $parent = this;
            var $el = this.find(opt.handle);
        }

        return $el.css('cursor', opt.cursor).on("mousedown", function (e) {
            if (opt.handle === "") {
                var $drag = $(this).addClass('draggable');
            } else {
                $(this).addClass('active-handle')
                var $drag = $parent.addClass('draggable');
            }

            var
            drg_h = $drag.outerHeight(),
                drg_w = $drag.outerWidth(),
                pos_y = $drag.offset().top + drg_h - e.pageY,
                pos_x = $drag.offset().left + drg_w - e.pageX;

            follow = function (e) {
                $drag.offset({
                    top: e.pageY + pos_y - drg_h,
                    left: e.pageX + pos_x - drg_w
                })
            };

            $(window).on("mousemove", follow).on("mouseup", function () {
                $drag.removeClass('draggable');
                $(window).off("mousemove", follow);
            });

            e.preventDefault(); // disable selection

        }).on("mouseup", function () {
            if (opt.handle === "") {
                $(this).removeClass('draggable');
            } else {
                $(this).removeClass('active-handle');
                $parent.removeClass('draggable');
            }
        });

    }
})(jQuery);

$("#dragme").drags({}, function (e) {});
like image 693
bryan Avatar asked Mar 03 '15 00:03

bryan


2 Answers

Here are a few of my findings to make sure dragging on a scaled container works for method one. The only caveat is to make sure you have var percent as the scaled percentage declared before any of these actions happen.

First, use this code at the top of your javascript. This wil help making sure that the droppable area works with a sacled container.

$.ui.ddmanager.prepareOffsets = function( t, event ) { var i, j, m = $.ui.ddmanager.droppables[ t.options.scope ] || [], type = event ? event.type : null, list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack(); droppablesLoop: for ( i = 0; i < m.length; i++ ) { if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ], ( t.currentItem || t.element ) ) ) ) { continue; } for ( j = 0; j < list.length; j++ ) { if ( list[ j ] === m[ i ].element[ 0 ] ) { m[ i ].proportions().height = 0; continue droppablesLoop; }  } m[ i ].visible = m[ i ].element.css( "display" ) !== "none"; if ( !m[ i ].visible ) { continue; } if ( type === "mousedown" ) { m[ i ]._activate.call( m[ i ], event ); } m[ i ].offset = m[ i ].element.offset(); m[ i ].proportions({ width: m[ i ].element[ 0 ].offsetWidth * percent, height: m[ i ].element[ 0 ].offsetHeight * percent }); } };

Here are a few functions that are necessary to fix the drag so it works on a scaled container.

function dragFix(event, ui) { var changeLeft = ui.position.left - ui.originalPosition.left, newLeft = ui.originalPosition.left + changeLeft / percent, changeTop = ui.position.top - ui.originalPosition.top, newTop = ui.originalPosition.top + changeTop / percent; ui.position.left = newLeft; ui.position.top = newTop; }

function startFix(event, ui) { ui.position.left = 0; ui.position.top = 0;  var element = $(this); }

You will want this if you want to enable the element to be resizable on a scaled container.

function resizeFix(event, ui) { var changeWidth = ui.size.width - ui.originalSize.width, newWidth = ui.originalSize.width + changeWidth / percent, changeHeight = ui.size.height - ui.originalSize.height, newHeight = ui.originalSize.height + changeHeight / percent;  ui.size.width = newWidth; ui.size.height = newHeight; }

To make an element draggable, I use the following function.

$("ELEMENT").resizable({ minWidth: - ($(this).width()) * 10, minHeight: - ($(this).height()) * 10, resize: resizeFix, start: startFix });
$("ELEMENT").draggable({ cursor: "move", start: startFix, drag: dragFix }); }
like image 110
user3442467 Avatar answered Sep 19 '22 05:09

user3442467


A similar problem is mentioned here: jquery - css "transform:scale" affects '.offset()' of jquery

It seems the problem arises from the fact that jQuery fails to return exact size for scaled elements and therefore failing setting right offset values to the element.

To solve this, he is suggesting first setting scale to 1 and setting offset and then again resetting scale value.

But this alone does not solve the problem here. Since mouse position is taken while it is scaled, position values should also be divided by scale value.

Here is an edited version of code:

  var scl = 2.5;
  var
    drg_h = $drag.outerHeight(),
    drg_w = $drag.outerWidth(),
    pos_y = $drag.offset().top/scl + drg_h - e.pageY/scl,
    pos_x = $drag.offset().left/scl + drg_w - e.pageX/scl;

  follow = function(e) {
    var size = {
      top:e.pageY/scl + pos_y - drg_h+scl*2,
      left:e.pageX/scl + pos_x - drg_w+scl*2
    };
      $drag.parent().css("transform","scale(1)");
      $drag.offset(size);
      $drag.parent().css("transform","scale("+scl+")");
  };

Note: I only replaced scale value for transform tag, since I am using chrome. You can also replace all instances or instead you can use a different class with 1 scale value.

JSFiddle is also here.

like image 41
fatih Avatar answered Sep 20 '22 05:09

fatih