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

Posted

技术标签:

【中文标题】可拖动元素在缩放 div 上的准确放置【英文标题】:Accurate drop for draggable element on scaled div 【发布时间】:2017-02-20 14:36:27 【问题描述】:

问题

我在将元素拖到可伸缩的 div 容器上时遇到了一个小问题。

一旦元素实际在容器中,元素就会很好地拖动并按照它们应有的方式工作。

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

但是当拖动较小的元素时,您会看到鼠标不再附着在所述元素上,并且当它被放下时,它会从应该放下的位置稍微下降。

我正在尝试找到一个解决方案,让我的鼠标停留在元素上,然后它会掉到应该掉的地方。

我已经一点一点地解决了问题,你可以在下面看到,但这是让我发疯的最后一块拼图。如果有人有时间伸出援助之手,我们将非常感谢

这是一个 codepen - 单击并将两个蓝色元素拖到白色容器上进行尝试

Codepen

Full Screen View

Short GIF in Action


这将有助于确保可放置区域与缩放的容器一起使用。

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

【问题讨论】:

我认为问题源于这样一个事实,即当您从右栏中拖动蓝色框时,使用鼠标移动的重复元素相对于元素的左上角定位,忽略自动左/右页边距。 @Terry 有什么建议可以解决这个问题吗? 我不了解您的实际问题。你能详细说明一下吗 @bryan 你检查我的答案了吗? 没问题,很高兴能帮上忙。请注意,如果您将其缩减到基本要素,您的问题会做得更好。 codepen中有很多多余的代码,很难涉水 【参考方案1】:

对于拖动时的光标位置,请参阅此答案:Make Cursor position in center for ui.helper in jquery-ui draggable method

基本上,您可以控制实例的光标位置,允许拥有比 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上,你需要考虑变换,但是你可以通过使用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

【讨论】:

哇!我花了一年多的时间来解决这个问题,你终于做到了!太感谢了!我没有发现任何问题【参考方案2】:

看起来是什么导致这看起来很奇怪:

首先,小 div 的样式为 display: block。这意味着即使 div 看起来很小,但该元素实际上会延伸到整个容器。

其次,一旦你在左侧屏幕上显示拖动的正方形,鼠标光标和元素整体之间的关系在技术上是居中的,但是你正在将原始元素的大小削减到更小的一个,并且当宽度和高度减小,结果从原始 div 的左上角开始以新的宽度和高度呈现。 (如果您将小按钮设置为display: inline,您会明白我的意思。尝试从左上角抓住它,然后尝试从右下角抓住它。您会看到前者看起来不错,但后者已关闭) .

所以我的建议是:

    制作可拖动元素display: inline 使左侧屏幕上拖动的元素与右侧屏幕上原始元素的高度和宽度完全相同。

希望有帮助!

【讨论】:

【参考方案3】:

我已经对你的 codepen 进行了分叉并使用它。

Take a look at it HERE,看看它是否能帮你找到“bug”。

对于您的draggable 脚本,我将代码更改为此,添加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 之间拖放“小”元素时,您是否能够比较鼠标光标位置?这可能会帮助您了解已解决的问题(尽管我重复一遍,但它不是 100% 准确的,因此有“调整”和“抛光”两个词。【参考方案4】:

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

cursorAt:  top: 6, left: -100 

还有“sm_textbox_item”的top和left参数有一点变化。

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

对于大框,需要对顶部和左侧元素进行一些调整(笔已更新)。

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

我叉了你的笔,做了一些改变。我知道这不是一个完美的解决方案,我也在尝试一点一点地解决这个问题。你可以check it here

【讨论】:

感谢您的尝试。较大的文本框似乎不适用于将光标放在框上以减小窗口大小;即使有上述变化 看起来你已经修复了小盒子,但似乎以某种方式打破了大盒子。把窗口变小,你会看到。【参考方案5】:

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

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

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

【讨论】:

删除边距会降低效果,但不能修复。【参考方案6】:

当您将transform 添加到元素的样式中,然后使其可拖动时,会出现此问题。您必须在没有transform 的情况下凑合着使用才能获得完美的结果。我花了 2 天时间调试才发现,我不想让其他人经历那种痛苦。

【讨论】:

欢迎来到 ***。 Aanand,请说明:如果使元素可拖动然后添加transform,这是否可行?您的答案中的代码示例也会有所帮助。最好的问候 我相信你的第一个建议是行不通的。然而,显然,使用.animate(transform: [transform]) 确实工作。

以上是关于可拖动元素在缩放 div 上的准确放置的主要内容,如果未能解决你的问题,请参考以下文章

拖放到缩放的元素上

缩放容器的jquery可拖动包含数组值

如何实现拖动修改网页中div模块大小

使用 CSS 变换缩放的 jQuery 拖动/调整大小

缩放网页内容上的特定元素(HTML、CSS、JavaScript)

当div可滚动时,div中的可拖动元素显示在放置区域的后面,需要它们显示在前面