Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
108 views
in Technique[技术] by (71.8m points)

javascript - Accurate drop for draggable element on scaled div

THE PROBLEM

I'm having a minor problem dragging elements onto a scalable div container.

Once the element is actually in the container, the elements drag fine and work the way they are supposed to.

Larger elements that are dragged onto the scalable container don't have too much of an issue.

But when smaller elements are dragged, you can see that the mouse is no longer attached to said element and when it is dropped, it drops a little off where it is supposed to drop.

I'm trying to find a solution that my mouse stays on the element and it drops where it is supposed to drop.

I've solved problems bit by bit and you can see below but this is the last piece of the puzzle that's driving me mad. If anyone has the time to lend a hand, it would be greatly appreciated.

Here is a codepen - click and drag the two blue elements onto the white container to try it out

Codepen

Full Screen View

Short GIF in Action


This wil help making sure that the droppable area works with a scaled 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
    });
  }
};

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;
}

Makes it so drag works on a scaled container

function dragFix(event, ui) { 
    var containmentArea = $("#documentPage_"+ui.helper.parent().parent().attr('id').replace(/^(w+)_/, "")),
        contWidth = containmentArea.width(), contHeight = containmentArea.height();
    ui.position.left = Math.max(0, Math.min(ui.position.left / percent , contWidth - ui.helper.width()));
    ui.position.top = Math.max(0, Math.min(ui.position.top  / percent,  contHeight- ui.helper.height()));
}

Creating a draggable element that I can drag onto the box.

.directive('draggableTypes', function() {
  return {
    restrict:'A',
    link: function(scope, element, attrs) {
      element.draggable({
        zIndex:3000, 
        appendTo: 'body', 
        helper: function(e, ui){ 
          var formBox = angular.element($("#formBox"));
          percent = formBox.width() / scope.templateData.pdf_width;
          if(element.attr('id') == 'textbox_item')
              return $('<div class="text" style="text-align:left;font-size:14px;width:200px;height:20px;line-height:20px;">New Text Box.</div>').css({ 'transform': 'scale(' + percent + ')', '-moz-transform': 'scale(' + percent + ')', '-webkit-transform': 'scale(' + percent + ')', '-ms-transform': 'scale(' + percent + ')'});
          if(element.attr('id') == 'sm_textbox_item')
              return $('<div class="text" style="text-align:left;font-size:14px;width:5px;height:5px;line-height:20px;"></div>').css({ 'transform': 'scale(' + percent + ')', '-moz-transform': 'scale(' + percent + ')', '-webkit-transform': 'scale(' + percent + ')', '-ms-transform': 'scale(' + percent + ')'});
          }
      });
    }
  };
})

Create draggable/resizable elements that may already be in the box and applying the drag/resize fix to these

.directive('textboxDraggable', function() {
  return {
    restrict:'A',
    link: function(scope, element, attrs) {

        element.draggable({ 
            cursor: "move",
            drag: dragFix,
            start: function(event, ui) {
                var activeId = element.attr('id');
                scope.activeElement.id = activeId;
                scope.activeElement.name = scope.templateItems[activeId].info.name;
                scope.$apply();
            }
        });

        element.resizable({
            minWidth: 25,
            minHeight: 25,
            resize: resizeFix,
            stop: function( event, ui ) {

                var activeId = element.attr('id');

                scope.activeElement.duplicateName = false;
                scope.activeElement.id = activeId;
                scope.activeElement.name = scope.templateItems[activeId].info.name;

                scope.templateItems[activeId]['style']['width'] = element.css('width');
                scope.templateItems[activeId]['style']['height'] = element.css('height');

                scope.$apply();
            }
        })

    }
  };
})

What happens when an item is dropped

.directive('droppable', function($compile) {
  return {
    restrict: 'A',
    link: function(scope,element,attrs){
        element.droppable({
            drop:function(event,ui) {
                 var draggable = angular.element(ui.draggable),
                    draggable_parent = draggable.parent().parent(),
                    drag_type = draggable.attr('id'),
                    documentBg = element,
                    x = ui.offset.left,
                    y = ui.offset.top,
                    element_top = (y - documentBg.offset().top - draggable.height() * (percent - 1) / 2) / percent,
                    element_left = (x - documentBg.offset().left - draggable.width() * (percent - 1) / 2) / percent,
                    timestamp = new Date().getTime();

                    //just get the document page of where the mouse is if its a new element
                    if(draggable_parent.attr('id') == 'template_builder_box_container' || draggable_parent.attr('id') == 'template_builder_container')
                        var documentPage = documentBg.parent().parent().attr('id').replace(/^(w+)_/, "");
                    //if you are dragging an element that was already on the page, get parent of draggable and not parent of where mouse is
                    else var documentPage = draggable_parent.parent().parent().attr('id').replace(/^(w+)_/, "");

                    if(drag_type == "textbox_item")
                    {
                        scope.activeElement.id = scope.templateItems.push({
                            info: {'page': documentPage,'name': 'textbox_'+timestamp, 'type': 'text'},
                            style: {'text-align':'left','font-size':'14px','top':element_top+'px','left':element_left+'px', 'width':'200px', 'height':'20px'}
                        }) - 1;

                        scope.activeElement.name = 'textbox_'+timestamp;
                    }
                    else if(drag_type == "sm_textbox_item")
                    {
                        scope.activeElement.id = scope.templateItems.push({
                            info: {'page': documentPage,'name': '', 'type': 'text'},
                            style: {'text-align':'left','font-size':'14px','top':element_top+'px','left':element_left+'px', 'width':'5px', 'height':'5px'}
                        }) - 1;

                        scope.activeElement.name = 'textbox_'+timestamp;
                    }
                    else {
                        scope.templateItems[scope.activeElement.id]['style']['top'] = draggable.css('top');
                        scope.templateItems[scope.activeElement.id]['style']['left'] = draggable.css('left');
                    }

                scope.$apply();
            }
        });
    }
  };
})

last but not least, my controller

.controller('testing', function($scope, $rootScope, $state, $stateParams) {
  $scope.templateItems = [];
  $scope.activeElement = { id: undefined, name: undefined };
  $scope.templateData = {"id":"12345", "max_pages":1,"pdf_width":385,"pdf_height":800};
  $scope.clickElement = function(index)   { $scope.activeElement = { id: index, name: $scope.templateItems[index].info.name } }

});

Here is the basis of my html

<div id="formBox" ng-style="formbox(templateData.pdf_width)" zoom>
    <div class="trimSpace" ng-style="trimSpace(templateData.pdf_width)" zoom>
        <div id="formScale" ng-style="formScale(templateData.pdf_width)" zoom>
            <form action="#" id="{{ templateData.id }}_form">
                <div ng-repeat="key in [] | range:templateData.max_pages">              
                    <div class="formContainer" id="{{ templateData.id + '_' + (key+1) }}" ng-style="{width: templateData.pdf_width+'px', height: templateData.pdf_height+'px'}">
                        <div class="formContent">
                            <div class="formBackground" id="documentPage_{{ (key+1) }}" droppable>
                                <div ng-hide="preview" ng-repeat="item in templateItems">
                                    <div ng-if="item.info.page == (key+1) && item.info.type == 'text'" id="{{ $index }}" data-type="{{ item.info.type }}" ng-click="clickElement($index)" class="text" ng-style="item.style" textbox-draggable>{{ item.info.name }}</div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>          
            </form>
        </div>
    </div>
</div>
question from:https://stackoverflow.com/questions/39987643/accurate-drop-for-draggable-element-on-scaled-div

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

For the cursor position while dragging, see this answer : Make Cursor position in center for ui.helper in jquery-ui draggable method

Basically, you can control the cursor position of the instance, allowing to have something more dynamic that cursorAt. Like this:

start: function(event, ui){
    $(this).draggable('instance').offset.click = {
        left: Math.floor(ui.helper.width() / 2),
        top: Math.floor(ui.helper.height() / 2)
    }
},

Then on the drop, you need to take into account the transform, but you can simplify by using the helper coordinates instead of the draggable. Like this:

element_top = (ui.helper.offset().top / percent) - (documentBg.offset().top / percent);
element_left = (ui.helper.offset().left / percent) - (documentBg.offset().left / percent);

Result: https://codepen.io/anon/pen/jamLBq


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...