简体   繁体   中英

Moving draggable element between relatively positioned containers

I have a page containing items that can be dropped into various containers. I have to prepend the draggable item to the container it is currently over at any given time in order to style it in a certain way according to the css, dependent on a combination of the draggable's and the drop target's class. Applying this style in javascript isn't an option - the styling has to come from CSS.

The problem comes because the containers I am prepending the draggable to have the position: relative attribute. This messes up with the draggable helper's position relative to my mouse when I'm moving it along.

Here's a very simplified demo of the issue:

 $('.draggable').draggable({ drag: _onDrag, helper: function () { return $(this).clone().prependTo('#div1'); } }); function _onDrag(event, ui) { ui.helper.hide(); var elementAtPoint = $(document.elementFromPoint(event.clientX, event.clientY)); if(elementAtPoint.hasClass('dropTarget')) { ui.helper.prependTo(elementAtPoint); } ui.helper.show(); }
 .draggable { display: inline-block; background: red; height:50px; width: 50px; } #div1 .item1 { background: green; } #div1 .item2 { background: purple; } #div2 .item1 { background: blue; } #div2 .item2 { background: orange; } #div1, #div2 { position: relative; width: 100px; height: 100px; border: 1px solid #000; display: inline-block; }
 <script src="https://code.jquery.com/jquery-2.2.2.min.js"></script> <script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script> <div class="draggable item1"> Drag me! </div> <div class="draggable item2"> Drag me too! </div> <div id="div1" class="dropTarget"> Drag the box here first... </div> <div id="div2" class="dropTarget"> Then drag it here :( </div>

Is there anything I can do to prevent the draggable from losing it's position, without changing the css or html?

This happens because the render position is recalculated based on the new offset when the DOM is rearranged. The simplest way to fix it is to not rearrange the DOM. Have you considered using droppable widgets? They provide the functionality you're looking for pretty much out-of-the-box:

 $('#draggable').draggable({ helper: function () { return $(this).clone().prependTo('#div1'); } }); $('#div1, #div2').droppable({ over: function(e, ui) { ui.helper.addClass($(this).data('target-class')); }, out: function(e, ui) { ui.helper.removeClass($(this).data('target-class')); } });
 #draggable { display: inline-block; background: red; height:50px; width: 50px; } #draggable.green { background: green; } #draggable.blue { background: blue; } #div1, #div2 { position: relative; width: 100px; height: 100px; border: 1px solid #000; display: inline-block; }
 <script src="https://code.jquery.com/jquery-2.2.2.min.js"></script> <script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script> <div id="draggable"> Drag me! </div> <div id="div1" class="dropTarget" data-target-class="green"> Drag the box here first... </div> <div id="div2" class="dropTarget" data-target-class="blue"> Then drag it here :) </div>

Update: in order to do this without any CSS changes, you can get the difference between the containers' positions, and use it as a position offset.

 $('.draggable').draggable({ start: _onStart, drag: _onDrag, helper: function () { return $(this).clone().prependTo('#div1'); } }); function _onStart(event, ui) { this.initialContainer = ui.helper.parent(); this.currentContainer = ui.helper.parent(); this.topOffset = 0; this.leftOffset = 0; } function _onDrag(event, ui) { ui.helper.hide(); var elementAtPoint = $(document.elementFromPoint(event.clientX, event.clientY)); if(elementAtPoint.hasClass('dropTarget') && !elementAtPoint.is(this.currentContainer)) { this.topOffset = elementAtPoint.position().top - this.initialContainer.position().top; this.leftOffset = elementAtPoint.position().left - this.initialContainer.position().left; this.currentContainer = elementAtPoint; ui.helper.prependTo(elementAtPoint); } ui.position.top -= this.topOffset; ui.position.left -= this.leftOffset; ui.helper.show(); }
 .draggable { display: inline-block; background: red; height:50px; width: 50px; } #div1 .item1 { background: green; } #div1 .item2 { background: purple; } #div2 .item1 { background: blue; } #div2 .item2 { background: orange; } #div1, #div2 { position: relative; width: 100px; height: 100px; border: 1px solid #000; display: inline-block; }
 <script src="https://code.jquery.com/jquery-2.2.2.min.js"></script> <script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script> <div class="draggable item1"> Drag me! </div> <div class="draggable item2"> Drag me too! </div> <div id="div1" class="dropTarget"> Drag the box here first... </div> <div id="div2" class="dropTarget"> Then drag it here :) </div>

I'd still recommend pushing for rewriting the CSS if at all possible. The first approach is both more readable and less fragile

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM