Точное падение для перетаскиваемого элемента на масштабированном div

ПРОБЛЕМА

У меня есть небольшая проблема перетаскивания элементов в расширяемый контейнер div.

Как только элемент фактически находится в контейнере, элементы перетаскиваются в тон и работают так, как они должны.

Более крупные элементы, которые перетаскиваются на масштабируемый контейнер, не имеют слишком большой проблемы.

Но когда перетаскиваются меньшие элементы, вы можете видеть, что мышь больше не привязана к указанному элементу, а когда она отбрасывается, она немного опускается, где она должна упасть.

Я пытаюсь найти решение, которое моя мышь остается на элементе, и он падает там, где он должен упасть.

Я решил проблемы пополам, и вы можете видеть ниже, но это последний фрагмент головоломки, которая сводит меня с ума. Если у кого есть время, чтобы протянуть руку, мы будем очень благодарны.

Вот код-щелчок и перетащите два синих элемента на белый контейнер, чтобы попробовать его

Codepen

Полноэкранный просмотр

Короткое GIF в действии


Этот wil поможет убедиться, что область сбрасывается с масштабированным контейнером.

$.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
    });
  }
};

Разрешить изменение размера элемента в масштабированном контейнере

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

Делает так, чтобы перетаскивание выполнялось на масштабированном контейнере

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

Создание перетаскиваемого элемента, который можно перетащить в поле.

.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 + ')'});
          }
      });
    }
  };
})

Создайте перетаскиваемые/изменяемые размеры элементы, которые уже могут быть в поле, и применяя исправление перетаскивания/изменения размера к этим

.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();
            }
        })

    }
  };
})

Что происходит при отбрасывании элемента

.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();
            }
        });
    }
  };
})

последнее, но не менее важное: мой контроллер

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

});

Вот основа моего 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>

Ответ 1

Для позиции курсора при перетаскивании см. этот ответ: Сделать позицию курсора в центре для ui.helper в jquery-ui перетаскиваемом методе

В принципе, вы можете управлять позицией курсора экземпляра, позволяя иметь что-то более динамичное, что cursorAt. Вот так:

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

Затем в drop вам необходимо учесть это преобразование, но вы можете упростить, используя вспомогательные координаты вместо draggable. > . Вот так:

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

Результат: https://codepen.io/anon/pen/jamLBq

Ответ 2

Похоже, что это выглядит странно:

Во-первых, маленький div имеет стиль display: block. Это означает, что, хотя это выглядит так, как div мало, этот элемент фактически растягивает на нем весь контейнер.

Во-вторых, когда вы показываете перетаскиваемый квадрат на левом экране, связь между курсором мыши и элементом цели технически центрируется, но вы уменьшаете размер исходного элемента на меньший, а когда ширина и высота уменьшается, результат получается с новой шириной и высотой, начиная с верхнего левого угла исходного div. (Если вы нарисуете маленькую кнопку, чтобы быть display: inline, вы можете видеть, что я имею в виду. Попробуйте схватить ее из левого верхнего угла и попробуйте нижний правый. Вы увидите, что первый выглядит хорошо, но последний выключен).

Итак, мои предложения:

  • Сделайте элементы draggabble display: inline
  • Сделайте перетаскиваемый элемент на левом экране точной высотой и шириной исходного элемента на правом экране.

Надеюсь, что это поможет!

Ответ 3

Я расколол ваш код и играл с ним.

Посмотрите на это ЗДЕСЬ и посмотрите, поможет ли вам найти "ошибку".

Для вашего draggable script я изменил код на это, добавив margin-left и margin-right:

if(element.attr('id') == 'sm_textbox_item') { /* the small draggable box */
  var el = {
    pos: element.offset(), // position of the small box
    height: element.outerHeight() + 20,
    left: 0
  }
  var deduct = $('#formBox').innerWidth() - 20; // width of the element that left of small box container
  el.left = el.pos.left - deduct;

  return $('<div class="text" style="text-align:left; font-size:14px; width:5px; height:5px; line-height:20px;"></div>')
    .css({
      'margin-left': el.left + 'px',
      'margin-top': el.pos.top - el.height + 'px',

      'transform': 'scale(' + percent + ')',
      '-moz-transform': 'scale(' + percent + ')',
      '-webkit-transform': 'scale(' + percent + ')',
      '-ms-transform': 'scale(' + percent + ')'
    });
}

Затем для вашего droppable script я изменил формулу для element_top и element_left:

// old formula
element_top = (y - documentBg.offset().top - draggable.height() * (percent - 1) / 2) / percent
element_left = (x - documentBg.offset().left - draggable.width() * (percent - 1) / 2) / percent

// new formula
element_top = (y - documentBg.offset().top) / (percent * 0.915)
element_left = (x - documentBg.offset().left) / (percent * 0.915)

Он дает "почти" точный результат, но вы можете настроить его дальше, чтобы отполировать его. Надеюсь, это поможет.

Ответ 4

Для прикрепления элементов с курсором во время перетаскивания вам просто нужно использовать

cursorAt: { top: 6, left: -100 }

И небольшое изменение в верхнем и левом параметрах "sm_textbox_item".

top: (y - documentBg.offset().top) / (percent) + "px",
left: (x - documentBg.offset().left) / (percent) + "px",

Для большого окна снова требуется настройка в верхнем и левом элементе (обновление пера).

top: element_top-3, left: element_left+6.49,

Я развернул ручку и сделал некоторые изменения. Я знаю, что это не идеальное решение, я также пытаюсь решить это по частям. Вы можете проверить его здесь

Ответ 5

@ITWitch прав, в draggable() должна быть некоторая ошибка. Стиль margin: 0 auto; в #sm_textbox_item является источником проблемы.

Попробуйте добавить это к перетаскиваемым параметрам в директиве draggableType, чтобы исправить положение:

cursorAt: {left: -parseInt(window.getComputedStyle(element[0],null,null)['margin-left'])},

Ответ 6

Эта проблема возникает, когда вы добавляете стиль transform к стилю элемента, а затем перетаскиваете его. Вам нужно обойтись без transform, чтобы добиться отличного результата. Я потратил 2 дня на отладку, пока не узнал, и я не хотел, чтобы кто-то другой пережил эту боль.