可拖动元素在缩放div上的准确落点

42

问题

我在可缩放的 div 容器中拖拽元素时遇到了一些小问题。

一旦元素实际进入容器,元素就可以正常拖动并按照预期工作。

被拖到可缩放容器上的较大元素没有太大问题。

但是当拖动较小的元素时,你会发现鼠标不再与该元素相连,释放时也会偏离应该落下的位置。

我正在尝试找到一个解决方案,让我的鼠标停留在元素上,并将元素放置在应该放置的位置。

我已经逐步解决了问题,你可以在下面看到,但这是让我发疯的最后一块拼图。如果有人有时间帮忙,那将非常感激。

这里是一个 Codepen - 点击并将两个蓝色元素拖到白色容器中以进行尝试

Codepen

全屏查看

短 GIF 演示


这将有助于确保可缩放容器中的可放置区域正常工作。

$.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基础代码:

这里是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>

我认为问题的根源在于当您从右侧列拖动蓝色框时,随鼠标移动的重复元素相对于元素的左上角定位,忽略了自动的左/右边距。 - Terry
@Terry,你有什么建议可以帮我解决这个问题吗? - bryan
我不理解你的实际问题。你能详细说明一下吗? - Aravind
@Bryan,你看过我的答案了吗? - Nedev
1
没问题,很高兴能够帮助到你。请注意,如果你将问题缩减到基本要素,那么你的问题会更好地得到解决。在 Codepen 中有很多多余的代码,这使得阅读非常困难。 - K Scandrett
@bryan 我不确定这能有多大帮助,但我最近遇到了类似的问题,而我所做的只是在被拖动的元素上设置 display:block。我在你的codepen中尝试过了,但它没有起作用,所以那可能不是问题所在。如果你正在使用JQuery,则还有一些其他选项可以帮助解决这个问题。例如,我将“tolerance”设置为“touch”。再次强调,不确定它是否有所帮助,但任何见解都可能让你更接近答案。 - garek007
6个回答

5

关于拖动时光标的位置,请参见此答案:使用jquery-ui可拖动方法中的ui.helper使光标位置居中

基本上,可以控制实例的光标位置,从而实现比cursorAt更具动态性的效果。例如:

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

接下来在 拖动 操作中,您需要考虑变换,但您可以简化操作,使用 helper 坐标代替 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

哇!我花了一年多的时间才解决这个问题,你终于做到了!非常感谢你!我没有看到任何问题。 - bryan

1
看起来导致这个看起来奇怪的原因是以下内容:
首先,小div被样式化为“display: block”。这意味着即使看起来div很小,该元素实际上会延伸到整个容器。
其次,一旦在左侧屏幕上显示拖动的正方形,鼠标光标和元素整体之间的关系在技术上是居中的,但您正在将原始元素的大小削减为较小的元素,当宽度和高度减少时,结果将从原始div的左上角开始呈现新的宽度和高度。(如果将小按钮样式设置为“display:inline”,您可以了解我的意思。尝试从左上角抓取它,然后尝试右下角。您会发现前者看起来很好,但后者偏离了)。
所以我的建议是:
1. 使可拖动元素“display: inline” 2. 使左侧屏幕上拖动的元素与右侧屏幕上的原始元素具有相同的高度和宽度。
希望这有所帮助!

0

@ITWitch 是对的,draggable() 中一定有一些 bug。 在 #sm_textbox_item 样式中的 margin: 0 auto; 是问题的根源。

尝试将以下内容添加到您的 draggableType 指令的可拖动选项中以更正位置:

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


去除边距可以减少影响,但不能完全解决问题。 - bryan

0

当你给一个元素添加transform样式并使其可拖动时,就会出现这个问题。为了得到完美的结果,你必须放弃使用transform。我花了两天时间进行调试,最终找到了问题所在,我不希望其他人也经历同样的痛苦。


欢迎来到StackOverflow。Aanand,请明确:如果将一个元素设为可拖动,然后添加transform,这是否有效?在您的答案中提供代码示例也会很有帮助。最好的问候。 - YakovL
我认为你的第一个建议不起作用。然而,显然使用.animate({transform: [transform]})是有效的。 - Aanand Kainth

0

如果您想在拖动过程中使用光标附加元素,只需使用

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,

我复制了你的笔并做了一些更改。我知道这不是一个完美的解决方案,我也在逐步解决这个问题。您可以在这里检查


我感谢你的尝试。即使进行了上述更改,较大的文本框似乎无法在缩小窗口大小时保持光标在框内。 - bryan
看起来你已经修复了小盒子,但是似乎不知道如何破坏了大盒子。将窗口缩小一点就会发现。 - bryan

0
我已经 fork 了你的 CodePen 并对其进行了调试。
点击此处查看它,看看能否帮助你找到“bug”。
至于你的可拖拽脚本,我将代码更改为以下内容,并添加了margin-leftmargin-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's left of small box's 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脚本,我更改了element_topelement_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)

它可以提供“几乎”准确的结果,但您可能需要进一步调整以使其更加完美。希望这有所帮助。


@bryan,你能比较一下在我的codepen和OP的codepen中拖放“small”元素时鼠标光标的位置吗?这可能会帮助你看到已经解决了什么问题(尽管我要再次强调,这并不是100%准确的,因此需要进行“微调”和“优化”)。 - ITWitch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接