I am working on how to show guides when moving boxes like it is in Google Docs Drawing. I would prefer an open-source code or any type of guide before starting writing my own.
- I do not need drag-n-drop across multiple browser windows so i don't need HTML5 Drag-n-Drop.
- Also i am using jquery-ui-draggable for boxes.
jquery ui has this already built in, see this demo:
http://jqueryui.com/demos/draggable/#snap-to
if you insist on the guidlines you would maybe have to fork jqueryui or look at the source and see if you can extend it.
alternatively you could just add your own snapping-functions on top of jQuery ui, i've played with it for a bit,
and while it doesn't seem like fun at least it also doesn't seem to be very hard.
you can view the example on jsfiddle: http://jsfiddle.net/x7uMh/103/
update: this works ~ jQuery 1.9 + jQueryUI 1.9. it breaks in newest jquery+ui. couldn't be bothered to see what exactly the problem is, typically its only minor problems though.
just in case that site ever goes down, here's the code:
css
body{
font-family: courier new, courier;
font-size: 12px;
}
.draggable{
border: 1px solid #ccc;
display: inline-block;
cursor: move;
position: absolute;
}
.guide{
display: none;
position: absolute;
left: 0;
top: 0;
}
#guide-h{
border-top: 1px dashed #55f;
width: 100%;
}
#guide-v{
border-left: 1px dashed #55f;
height: 100%;
}
html
<div class="draggable">drag me!</div>
<div class="draggable">you can drag me too, if you like</div>
<div class="draggable">hep hep</div>
<div id="guide-h" class="guide"></div>
<div id="guide-v" class="guide"></div>
javascript (make sure to include jquery + jquery ui)
var MIN_DISTANCE = 10; // minimum distance to "snap" to a guide
var guides = []; // no guides available ...
var innerOffsetX, innerOffsetY; // we'll use those during drag ...
$( ".draggable" ).draggable({
start: function( event, ui ) {
guides = $.map( $( ".draggable" ).not( this ), computeGuidesForElement );
innerOffsetX = event.originalEvent.offsetX;
innerOffsetY = event.originalEvent.offsetY;
},
drag: function( event, ui ){
// iterate all guides, remember the closest h and v guides
var guideV, guideH, distV = MIN_DISTANCE+1, distH = MIN_DISTANCE+1, offsetV, offsetH;
var chosenGuides = { top: { dist: MIN_DISTANCE+1 }, left: { dist: MIN_DISTANCE+1 } };
var $t = $(this);
var pos = { top: event.originalEvent.pageY - innerOffsetY, left: event.originalEvent.pageX - innerOffsetX };
var w = $t.outerWidth() - 1;
var h = $t.outerHeight() - 1;
var elemGuides = computeGuidesForElement( null, pos, w, h );
$.each( guides, function( i, guide ){
$.each( elemGuides, function( i, elemGuide ){
if( guide.type == elemGuide.type ){
var prop = guide.type == "h"? "top":"left";
var d = Math.abs( elemGuide[prop] - guide[prop] );
if( d < chosenGuides[prop].dist ){
chosenGuides[prop].dist = d;
chosenGuides[prop].offset = elemGuide[prop] - pos[prop];
chosenGuides[prop].guide = guide;
}
}
} );
} );
if( chosenGuides.top.dist <= MIN_DISTANCE ){
$( "#guide-h" ).css( "top", chosenGuides.top.guide.top ).show();
ui.position.top = chosenGuides.top.guide.top - chosenGuides.top.offset;
}
else{
$( "#guide-h" ).hide();
ui.position.top = pos.top;
}
if( chosenGuides.left.dist <= MIN_DISTANCE ){
$( "#guide-v" ).css( "left", chosenGuides.left.guide.left ).show();
ui.position.left = chosenGuides.left.guide.left - chosenGuides.left.offset;
}
else{
$( "#guide-v" ).hide();
ui.position.left = pos.left;
}
},
stop: function( event, ui ){
$( "#guide-v, #guide-h" ).hide();
}
});
function computeGuidesForElement( elem, pos, w, h ){
if( elem != null ){
var $t = $(elem);
pos = $t.offset();
w = $t.outerWidth() - 1;
h = $t.outerHeight() - 1;
}
return [
{ type: "h", left: pos.left, top: pos.top },
{ type: "h", left: pos.left, top: pos.top + h },
{ type: "v", left: pos.left, top: pos.top },
{ type: "v", left: pos.left + w, top: pos.top },
// you can add _any_ other guides here as well (e.g. a guide 10 pixels to the left of an element)
{ type: "h", left: pos.left, top: pos.top + h/2 },
{ type: "v", left: pos.left + w/2, top: pos.top }
];
}
hope that helps,
best, hansi.
I have created a simple example with just borders line beside the draggable box.
It shows up when we drag on the box. View demo here
HTML:
<div id="canvas">
<div id="box">
<span class="topline"></span>
<span class="rightline"></span>
<span class="botline"></span>
<span class="leftline"></span>
</div>
</div>
CSS:
#canvas {width: 1000px;height: 800px;}
.topline{
position:absolute;
width: 1000%;
border-top:1px red dotted;
display:none;
vertical-align::middle;
margin-top:-7px;
margin-left:-250%;
}
.botline{
position:absolute;
width: 1000%;
bottom:-2px;
border-bottom:1px red dotted;
display:none;
vertical-align::middle;
margin-top:500px;
margin-left:-250%;
}
.leftline{
position:absolute;
height: 1000%;
left:-2px;
border-left:1px red dotted;
display:none;
vertical-align::middle;
margin-top:-250%;
}
.rightline{
position:absolute;
height: 1000%;
right:-2px;
border-right:1px red dotted;
display:none;
vertical-align::middle;
margin-top:-250%;
}
#box {
cursor: move;
border:1px solid black;
width:150px;
height:100px;
min-width:80px;
min-height:80px;
padding:5px;
background-color:#1196c1;
}
JavaScript:
$(function() {
$("#box").draggable({
containment: "#canvas",
drag: function() {
$(this).find($('.topline')).css('display', 'block');
$(this).find($('.rightline')).css('display', 'block');
$(this).find($('.botline')).css('display', 'block');
$(this).find($('.leftline')).css('display', 'block');
},
start: function() {
$(this).find($('.topline')).css('display', 'block');
$(this).find($('.rightline')).css('display', 'block');
$(this).find($('.botline')).css('display', 'block');
$(this).find($('.leftline')).css('display', 'block');
},
stop: function() {
$(this).find($('.topline')).css('display', 'none');
$(this).find($('.rightline')).css('display', 'none');
$(this).find($('.botline')).css('display', 'none');
$(this).find($('.leftline')).css('display', 'none');
}
});
});
for those who still trying to find a way of doing this, i create a fiddle
i used snap and $(this).data('draggable').snapElements;
drag: function(event, ui)
{
//var snapped = $(this).data('ui-draggable').snapElements; //## for new version of jquery UI
var snapped = $(this).data('draggable').snapElements;
/* Pull out only the snap targets that are "snapping": */
var snappedTo = $.map(snapped, function(element) {
//return element.snapping ? element.item : null;
return element.snapping ? element : null;
});
if((snappedTo[0].left + snappedTo[0].width) == $(this).offset().left)
{
console.log('right of snapped item');
$('#guide-v').css({'left': $(this).offset().left}).show();
}else
if((snappedTo[0].left) == $(this).offset().left)
{
console.log('left of snapped item');
$('#guide-v').css({'left': $(this).offset().left}).show();
}else $('#guide-v').hide();
if((snappedTo[0].top) == $(this).offset().top)
{
console.log('top of snapped item');
$('#guide-h').css({'top': $(this).offset().top}).show();
}else
if((snappedTo[0].top + snappedTo[0].height) == $(this).offset().top)
{
console.log('bottom of snapped item');
$('#guide-h').css({'top': $(this).offset().top}).show();
}else $('#guide-h').hide();
}
});
http://jsfiddle.net/j6zqN/1/
I got an answer from this question: Javascript drag/drop - Illustrator style 'smart guides'
I think this is what are looking for. I also improved it by supporting the line for the same side. Here is the JsFiddle: http://jsfiddle.net/yusrilmaulidanraji/A6CpP/120/
HTML
<div id="parent">
<div class="object1 dropped" style="left:0px;top:300px;background:#a00;"></div>
<div class="object2 dropped"></div>
<div class="object3 dropped" style="left:400px;top:20px;"></div>
<div class="objectx"></div>
<div class="objecty"></div>
</div>
CSS:
#parent{
width:600px;
height:500px;
border:1px solid #000;
position:relative;
}
.object1{
background:#aaa;
width:100px;
height:100px;
display:block;
position:absolute;
left:140px;
top:50px;
}
.object2{
background:#aaa;
width:100px;
height:150px;
display:block;
position:absolute;
left:140px;
top:50px;
}
.object3{
background:#aaa;
width:150px;
height:100px;
display:block;
position:absolute;
left:140px;
top:50px;
}
.objectx{
display:none;
//background:#fff;
width:0px;
height:100%;
position:absolute;
top:0px;
left:10px;
border-left: 1px solid yellow;
}
.objecty{
display:none;
//background:#fff;
width:100%;
height:0px;
position:absolute;
top:10px;
left:0px;
border-bottom: 1px solid yellow;
}
JS:
$.ui.plugin.add("draggable", "smartguides", {
start: function(event, ui) {
var i = $(this).data("draggable"), o = i.options;
i.elements = [];
$(o.smartguides.constructor != String ? ( o.smartguides.items || ':data(draggable)' ) : o.smartguides).each(function() {
var $t = $(this); var $o = $t.offset();
if(this != i.element[0]) i.elements.push({
item: this,
width: $t.outerWidth(), height: $t.outerHeight(),
top: $o.top, left: $o.left
});
});
},
stop: function(event, ui) {
$(".objectx").css({"display":"none"});
$(".objecty").css({"display":"none"});
},
drag: function(event, ui) {
var inst = $(this).data("draggable"), o = inst.options;
var d = o.tolerance;
$(".objectx").css({"display":"none"});
$(".objecty").css({"display":"none"});
var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height,
xc = (x1 + x2) / 2, yc = (y1 + y2) / 2;
for (var i = inst.elements.length - 1; i >= 0; i--){
var l = inst.elements[i].left, r = l + inst.elements[i].width,
t = inst.elements[i].top, b = t + inst.elements[i].height,
hc = (l + r) / 2, vc = (t + b) / 2;
var lss = Math.abs(l - x1) <= d;
var ls = Math.abs(l - x2) <= d;
var rss = Math.abs(r - x2) <= d;
var rs = Math.abs(r - x1) <= d;
var tss = Math.abs(t - y1) <= d;
var ts = Math.abs(t - y2) <= d;
var bss = Math.abs(b - y2) <= d;
var bs = Math.abs(b - y1) <= d;
var hs = Math.abs(hc - xc) <= d;
var vs = Math.abs(vc - yc) <= d;
if(lss) {
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
$(".objectx").css({"left":ui.position.left,"display":"block"});
}
if(rss) {
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
$(".objectx").css({"left":ui.position.left + ui.helper.width(),"display":"block"});
}
if(ls) {
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
$(".objectx").css({"left":ui.position.left + ui.helper.width(),"display":"block"});
}
if(rs) {
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
$(".objectx").css({"left":ui.position.left,"display":"block"});
}
if(tss) {
ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
$(".objecty").css({"top":ui.position.top,"display":"block"});
}
if(ts) {
ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
$(".objecty").css({"top":ui.position.top + ui.helper.height(),"display":"block"});
}
if(bss) {
ui.position.top = inst._convertPositionTo("relative", { top: b-inst.helperProportions.height, left: 0 }).top - inst.margins.top;
$(".objecty").css({"top":ui.position.top + ui.helper.height(),"display":"block"});
}
if(bs) {
ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
$(".objecty").css({"top":ui.position.top,"display":"block"});
}
if(hs) {
ui.position.left = inst._convertPositionTo("relative", { top: 0, left: hc - inst.helperProportions.width/2 }).left - inst.margins.left;
$(".objectx").css({"left":ui.position.left + (ui.helper.width()/2),"display":"block"});
}
if(vs) {
ui.position.top = inst._convertPositionTo("relative", { top: vc - inst.helperProportions.height/2, left: 0 }).top - inst.margins.top;
$(".objecty").css({"top":ui.position.top + (ui.helper.height()/2),"display":"block"});
}
};
}
});
$('.dropped').draggable({
containment: 'parent',
smartguides:".dropped",
tolerance:5
});