Collision Detection jQuery Move
I have the following HTML:
<div class="list" id="list"> <div class="item" id="i1">Item 1</div> <div class="item" id="i2">Item 2</div> <div class="item" id="i3">Item 3</div> </div> <div class="timeline" id="timeline"> </div> What I want to have using jQuery:
- Be able to drag
.itemfrom#listto#timeline .itemcan be dropped to the timeline as many times as needed, for example. there can be 4#i1elements in the timeline..itemon the timeline should not overlap.itemcan be positioned anywhere along the timeline if they do not overlap other elements on the timeline.
So, I went to jQueryUI Draggable and Droppable, and also went to jQueryUI Draggable Collision Plugin .
Here is the jQuery I started with:
$('#list .item').draggable({ helper: 'clone', revert: 'invalid', //the following are for the jquery-ui-dragggable-collision plugin obstacle: '#timeline .item', preventCollision: true }); $('#timeline').droppable({ accept: '.item' }); My problem is that jQueryUI Draggable Collision Plugin only works when you drag the original Div and not drag the helper. I need helpers so that I can reach # 2 (by adding multiple copies of one item). But I need something like a Collision plugin, so I can reach # 3 (elements do not overlap).
Does anyone know a solution to this problem? Is there another plugin that detects collisions on the drag & drop helper? Is there any other approach that I can try to get what I want to achieve?
If you prefer jsfiddle to use jQueryUI Draggable Collision Plugin, as you suggested, here's what you can play with: Link to jsfiddle
This approach uses the original helper to use the collision functions. The clone is generated in the start event function (and is deleted again in the stop event if the drag did not result in a successful deletion):
$(function(){ var draggableSelector = ".list .item:not(.dropped)"; var init = function() { $(draggableSelector).each(function(i){ $(this) .draggable({ //helper: 'clone', revert: 'invalid', start: function(event,ui) { var $clone = ui.helper.clone(); $clone .removeClass("ui-draggable ui-draggable-dragging") .insertAfter(ui.helper) ; $(this).data("clone",$clone); }, stop: function(event,ui) { if( $(".ui-draggable-dragging.dropped").length == 0) { $(this).data("clone").remove(); }; }, //the following are for the jquery-ui-draggable-collision plugin refreshPositions: true, obstacle: '.item.dropped', preventCollision: true, }) .css("left", ( ($(this).width() + 5) * i) + "px") ; }); $('.timeline').droppable({ accept: '.item' ,drop: function(event,ui) { ui.draggable .addClass("dropped") ; setTimeout(reinit, 500); } }); }; var reinit = function() { $(".list .item.ui-draggable").draggable("destroy"); init(); } init(); }); Hope this will be helpful.
Here is an example I wrote for this question showing a simple drag and drop plugin with conflict detection. This allows you to discard items on the timeline as long as there is room for an object to exist without overlapping.
This is by no means a finished product, but I hope it will show that such code is not incredibly difficult to write and an attempt to crack massive conflicting plugins is not always the best option. Sometimes it's better to start from scratch. It is fun and a really good way to find out.
jQuery
/*----------ON DOCUMENT READY----------*/ $(document).ready(function(){ $("#timeline").timeline({ items : ".item" }); }); /*----------THE TIMELINE PLUGIN----------*/ $.fn.timeline = function(options){ var defaults = { items : "div" } var options = $.extend(defaults,options) return this.each(function(){ //-----SETUP-----// //define all the vars we will need later var el = $(this); var items = $(options.items); var mousedown = false; var dragging = false; var activeItem = false; var placedItems = new Array(); //make everything unselectable so it dosne interfere with dragging $("html").find("*").css({ "user-select" : "none", "-moz-user-select" : "none", "-webkit-user-select" : "none", "-ms-user-select" : "none", "-o-user-select" : "none", }).attr("unselectable","true").unbind("onselectstart"); //-----EVENTS-----// //log when the mouse is down anywhere on the doc $(document).mousedown(function(){ mousedown = true; }); //when the mouse is released $(document).mouseup(function(e){ //if was dragging an item attempt to place it if(mousedown && dragging){ placeItem(e); } //log that dragging has stopped mousedown = false; dragging = false; }); //log when the mouse is pressed over an item items.mousedown(function(){ dragging = true; //clone the active item and hide it ready for dragging activeItem = $(this).clone().appendTo("body").hide(); }); //when the mouse movers over the doc $(document).mousemove(function(e){ //if mouse was pressed over item attempt to drag if(mousedown && dragging){ dragItem(e); } }); //-----FUNCTIONS-----// //drag the item around the screen function dragItem(e){ //if no active item done do owt if(!activeItem){ return false; } //work out where the drag anchor is var x = e.pageX-(activeItem.height()/2); var y = e.pageY-(activeItem.width()/2); //save the original position in case we cant place the item if(!activeItem.origPos){ activeItem.origPos = { x : x, y : y } } //drag the item activeItem.css({ "position" : "absolute", "top" : y, "left" : x, "z-index" : "999", "opacity" : 0.6, "display" : "block" }); } //attempt to place the item function placeItem(e){ //if no active item dont do owt if(!activeItem){ return false; } //define som vars needed later on var onTargetY = false; var onTargetX = false; var remove = false; var collision = false; //check if item is being relesed withing the timeline bounds if(e.pageY > el.position().top && e.pageY < el.position().top+el.height()){ onTargetY = true; } if(e.pageX > el.position().left && e.pageX < el.position().left+el.width()){ onTargetX = true; } //if on target attempt to drop on timeline if(onTargetX && onTargetY){ //snap to the left or right if dropped at the left or right edges var maxLeft = el.position().left; var maxRight = el.position().left+el.width()-activeItem.width(); x = e.pageX-(activeItem.width()/2); if(x < maxLeft){ x = maxLeft; }else if(x > maxRight){ x = maxRight; } //loop the items already on the timeline and check for collisions $.each(placedItems,function(i,item){ var itemMin = item.position().left; var itemMax = item.position().left+item.width(); if(x+activeItem.width() > itemMin && x < itemMax){ collision = true; } }); y = el.position().top; } //if there is a collision or the item is dropped outside the timeline //set x and y back to original position and set removal flag to true if(collision || !onTargetX || !onTargetY){ x = activeItem.origPos.x; y = activeItem.origPos.y; remove = true; //if dropped inside the timeline and no collisions add item to the //array of items inside the timeline }else{ placedItems.push(activeItem); } //finally either animate the item back to where it started then remove it //or snap it into the timeline in the space found activeItem.animate({ top : y, left : x },{ duration : 300, queue : false, complete : function(){ //if remove flag set remove the item from the dom if(remove){ $(this).remove(); } //some tidying up activeItem.css("opacity",1); activeItem = false; } }); } }); } html
<div class="list" id="list"> <div class="item item1">Item 1</div> <div class="item item2">Item 2</div> <div class="item item3">Item 3</div> </div> <div class="timeline" id="timeline"></div> Enjoy :).